docs: add comprehensive cursor guidelines and rules for development practices (#8401)
* Update cursor rules with specific backend, frontend, docs * Update image example Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * update image example Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Refactor frontend development guidelines: streamline sections, remove outdated icon development instructions, and update checklist for clarity and consistency. --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
34c55f14b4
commit
ab1ed8ea01
6 changed files with 1670 additions and 17 deletions
268
.cursor/rules/backend_development.mdc
Normal file
268
.cursor/rules/backend_development.mdc
Normal file
|
|
@ -0,0 +1,268 @@
|
||||||
|
---
|
||||||
|
description: "Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing."
|
||||||
|
globs:
|
||||||
|
- "src/backend/**/*.py"
|
||||||
|
- "tests/**/*.py"
|
||||||
|
- "Makefile"
|
||||||
|
- "pyproject.toml"
|
||||||
|
- "uv.lock"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# Backend Development Guidelines
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Guidelines for backend development in Langflow, focusing on Python components, FastAPI services, and backend testing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Backend Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- **Python Package Manager:** `uv` (>=0.4) for dependency management
|
||||||
|
- **Database:** SQLite for development, PostgreSQL for production
|
||||||
|
- **Development Tools:** `make` for build coordination
|
||||||
|
|
||||||
|
### Backend Service
|
||||||
|
```bash
|
||||||
|
make backend # Start FastAPI backend on port 7860
|
||||||
|
```
|
||||||
|
- Auto-reloads on file changes
|
||||||
|
- Health check: http://localhost:7860/health
|
||||||
|
- Backend components: `src/backend/base/langflow/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Component Development
|
||||||
|
|
||||||
|
### Component Structure
|
||||||
|
```
|
||||||
|
src/backend/base/langflow/components/
|
||||||
|
├── agents/ # Agent components
|
||||||
|
├── data/ # Data processing components
|
||||||
|
├── embeddings/ # Embedding components
|
||||||
|
├── input_output/ # Input/output components
|
||||||
|
├── models/ # Language model components
|
||||||
|
├── processing/ # Text processing components
|
||||||
|
├── prompts/ # Prompt components
|
||||||
|
├── tools/ # Tool components
|
||||||
|
└── vectorstores/ # Vector store components
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding New Components
|
||||||
|
1. **Location:** Add to appropriate subdirectory under `src/backend/base/langflow/components/`
|
||||||
|
2. **Import:** Update `__init__.py` with alphabetical imports:
|
||||||
|
```python
|
||||||
|
from .my_component import MyComponent
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"ExistingComponent",
|
||||||
|
"MyComponent", # Add alphabetically
|
||||||
|
]
|
||||||
|
```
|
||||||
|
3. **Auto-restart:** Backend auto-restarts on save
|
||||||
|
4. **Browser refresh:** Refresh browser to see component changes
|
||||||
|
|
||||||
|
### Component Testing
|
||||||
|
- **Unit Tests:** `src/backend/tests/unit/components/`
|
||||||
|
- **Test Structure:** Mirror component directory structure
|
||||||
|
- **Test Base Classes:** Use `ComponentTestBaseWithClient` or `ComponentTestBaseWithoutClient`
|
||||||
|
- **Version Testing:** Provide `file_names_mapping` for backward compatibility
|
||||||
|
|
||||||
|
### Development Tips
|
||||||
|
- **Fast iteration:** Edit component in UI first, then save to source
|
||||||
|
- **Component updates:** Old components show "Updates Available" after backend restart
|
||||||
|
- **Testing:** Create comprehensive unit tests for all new components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Backend Code Quality
|
||||||
|
|
||||||
|
### Formatting (CRITICAL)
|
||||||
|
```bash
|
||||||
|
make format_backend # Format Python code
|
||||||
|
```
|
||||||
|
**Important:** Run `make format_backend` _early and often_ (ideally before running linting or committing changes). It auto-corrects the majority of style issues, preventing lengthy manual fixes when lint errors surface later.
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
```bash
|
||||||
|
make lint # Run linting checks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
make unit_tests # Run backend unit tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pre-commit Workflow
|
||||||
|
1. **Run `make format_backend`** (FIRST - saves time on lint fixes)
|
||||||
|
2. Run `make lint`
|
||||||
|
3. Run `make unit_tests`
|
||||||
|
4. Commit changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. FastAPI Development
|
||||||
|
|
||||||
|
### API Structure
|
||||||
|
```
|
||||||
|
src/backend/base/langflow/api/
|
||||||
|
├── v1/ # API version 1
|
||||||
|
│ ├── chat.py # Chat endpoints
|
||||||
|
│ ├── flows.py # Flow management
|
||||||
|
│ ├── users.py # User management
|
||||||
|
│ └── ...
|
||||||
|
└── v2/ # API version 2 (future)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing APIs
|
||||||
|
- Use `client` fixture from `conftest.py`
|
||||||
|
- Test with `logged_in_headers` for authenticated endpoints
|
||||||
|
- Example:
|
||||||
|
```python
|
||||||
|
async def test_flows_endpoint(client, logged_in_headers):
|
||||||
|
response = await client.post(
|
||||||
|
"api/v1/flows/",
|
||||||
|
json=flow_data,
|
||||||
|
headers=logged_in_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Database Development
|
||||||
|
|
||||||
|
### Models Location
|
||||||
|
```
|
||||||
|
src/backend/base/langflow/services/database/models/
|
||||||
|
├── api_key/ # API key models
|
||||||
|
├── flow/ # Flow models
|
||||||
|
├── folder/ # Folder models
|
||||||
|
├── user/ # User models
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Testing
|
||||||
|
- Use in-memory SQLite for tests
|
||||||
|
- Database tests may fail in batch runs - run individually if needed:
|
||||||
|
```bash
|
||||||
|
uv run pytest src/backend/tests/unit/test_database.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Async Development Patterns
|
||||||
|
|
||||||
|
### Component Async Methods
|
||||||
|
```python
|
||||||
|
async def run(self) -> MessageType:
|
||||||
|
"""Main component execution method."""
|
||||||
|
# Use await for async operations
|
||||||
|
result = await self.async_operation()
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def message_response(self) -> Message:
|
||||||
|
"""Return a Message object for chat components."""
|
||||||
|
return Message(
|
||||||
|
text=self.input_value,
|
||||||
|
sender=self.sender,
|
||||||
|
session_id=self.session_id,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background Tasks
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def process_in_background(self):
|
||||||
|
"""Process items without blocking."""
|
||||||
|
# Use asyncio.create_task for background work
|
||||||
|
task = asyncio.create_task(self.heavy_operation())
|
||||||
|
|
||||||
|
# Ensure proper cleanup
|
||||||
|
try:
|
||||||
|
result = await task
|
||||||
|
return result
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# Handle cancellation gracefully
|
||||||
|
await self.cleanup()
|
||||||
|
raise
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue Operations
|
||||||
|
```python
|
||||||
|
async def queue_processing(self):
|
||||||
|
"""Non-blocking queue operations."""
|
||||||
|
queue = asyncio.Queue()
|
||||||
|
|
||||||
|
# Non-blocking put
|
||||||
|
queue.put_nowait(data)
|
||||||
|
|
||||||
|
# Timeout-controlled get
|
||||||
|
try:
|
||||||
|
result = await asyncio.wait_for(queue.get(), timeout=5.0)
|
||||||
|
return result
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
# Handle timeout appropriately
|
||||||
|
raise ComponentError("Processing timeout")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Component Integration Testing
|
||||||
|
|
||||||
|
### Flow Testing
|
||||||
|
```python
|
||||||
|
from tests.unit.build_utils import create_flow, build_flow, get_build_events
|
||||||
|
|
||||||
|
async def test_component_in_flow(client, json_flow, logged_in_headers):
|
||||||
|
"""Test component within a complete flow."""
|
||||||
|
flow_id = await create_flow(client, json_flow, logged_in_headers)
|
||||||
|
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||||
|
|
||||||
|
# Validate flow execution
|
||||||
|
job_id = build_response["job_id"]
|
||||||
|
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||||
|
assert events_response.status_code == 200
|
||||||
|
```
|
||||||
|
|
||||||
|
### External API Testing
|
||||||
|
```python
|
||||||
|
@pytest.mark.api_key_required
|
||||||
|
@pytest.mark.no_blockbuster
|
||||||
|
async def test_with_real_api(self):
|
||||||
|
"""Test component with external service."""
|
||||||
|
api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
component = MyComponent(api_key=api_key, model="gpt-4o")
|
||||||
|
|
||||||
|
response = await component.run()
|
||||||
|
assert response is not None
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Known Backend Issues
|
||||||
|
|
||||||
|
### Testing Quirks
|
||||||
|
- `test_database.py` may fail in batch runs but pass individually
|
||||||
|
- Use `@pytest.mark.no_blockbuster` to skip blockbuster plugin when needed
|
||||||
|
- Context variables may not propagate correctly in `asyncio.to_thread` - test both patterns
|
||||||
|
|
||||||
|
### File Changes
|
||||||
|
- Starter project files auto-format after `langflow run`
|
||||||
|
- These formatting changes can be committed or ignored
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backend Development Checklist
|
||||||
|
- [ ] Component added to appropriate subdirectory
|
||||||
|
- [ ] `__init__.py` updated with alphabetical imports
|
||||||
|
- [ ] Code formatted with `make format_backend` (FIRST)
|
||||||
|
- [ ] Linting passed with `make lint`
|
||||||
|
- [ ] Unit tests created and passing with `make unit_tests`
|
||||||
|
- [ ] Component tested in UI with backend restart + browser refresh
|
||||||
|
- [ ] Version mapping provided for backward compatibility
|
||||||
|
- [ ] Async patterns implemented correctly with proper cleanup
|
||||||
|
- [ ] External API calls use appropriate pytest markers
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
---
|
---
|
||||||
description: Rules and checklist for creating a basic Langflow Component
|
description: "Guide for creating a basic Langflow component, including requirements gathering, component structure, and implementation best practices."
|
||||||
globs:
|
globs:
|
||||||
|
- "src/backend/**/components/**/*.py"
|
||||||
|
- "src/backend/base/langflow/components/**/*.py"
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# Rule: How to Create a Basic Langflow Component
|
# Rule: How to Create a Basic Langflow Component
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
|
||||||
440
.cursor/rules/docs_development.mdc
Normal file
440
.cursor/rules/docs_development.mdc
Normal file
|
|
@ -0,0 +1,440 @@
|
||||||
|
---
|
||||||
|
description: "Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes."
|
||||||
|
globs:
|
||||||
|
- "docs/**/*.{md,mdx}"
|
||||||
|
- "docs/**/*.{js,jsx,ts,tsx}"
|
||||||
|
- "docs/package*.json"
|
||||||
|
- "docs/docusaurus.config.js"
|
||||||
|
- "docs/sidebars.js"
|
||||||
|
- "docs/static/**/*"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
# Documentation Development Guidelines
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Guidelines for developing and maintaining Langflow documentation using Docusaurus, including content structure, style, and deployment processes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Documentation Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- **Node.js:** v22.12 LTS for runtime
|
||||||
|
- **Package Manager:** Yarn for dependency management
|
||||||
|
- **Documentation Framework:** Docusaurus v3
|
||||||
|
|
||||||
|
### Documentation Service
|
||||||
|
```bash
|
||||||
|
cd docs
|
||||||
|
yarn install # Install dependencies
|
||||||
|
yarn start # Start dev server (usually port 3001)
|
||||||
|
```
|
||||||
|
- Auto-reloads on documentation changes
|
||||||
|
- Access at: http://localhost:3001/
|
||||||
|
- Documentation source: `docs/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Documentation Structure
|
||||||
|
|
||||||
|
### Directory Layout
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── docs/ # Main documentation content
|
||||||
|
│ ├── getting-started/ # Getting started guides
|
||||||
|
│ ├── components/ # Component documentation
|
||||||
|
│ ├── integrations/ # Third-party integrations
|
||||||
|
│ ├── administration/ # Admin and deployment guides
|
||||||
|
│ ├── contributing/ # Contribution guidelines
|
||||||
|
│ └── api-reference/ # API documentation
|
||||||
|
├── blog/ # Blog posts and announcements
|
||||||
|
├── src/ # Custom React components
|
||||||
|
├── static/ # Static assets (images, etc.)
|
||||||
|
├── sidebars.js # Sidebar configuration
|
||||||
|
├── docusaurus.config.js # Main configuration
|
||||||
|
└── package.json # Dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Types
|
||||||
|
- **Guides:** Step-by-step tutorials (`docs/getting-started/`)
|
||||||
|
- **Reference:** API and component reference (`docs/api-reference/`)
|
||||||
|
- **How-to:** Problem-solving articles (`docs/components/`)
|
||||||
|
- **Concepts:** Explanatory articles about Langflow concepts
|
||||||
|
- **Blog:** Release notes, announcements (`blog/`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Writing Documentation
|
||||||
|
|
||||||
|
### Markdown Conventions
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: Page Title
|
||||||
|
description: Brief description for SEO
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Page Title
|
||||||
|
|
||||||
|
Brief introduction paragraph.
|
||||||
|
|
||||||
|
## Section Header
|
||||||
|
|
||||||
|
Content with proper formatting.
|
||||||
|
|
||||||
|
### Subsection
|
||||||
|
|
||||||
|
More detailed content.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Use admonitions for important information.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
Use warnings for potential issues.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::danger
|
||||||
|
Use danger for critical warnings.
|
||||||
|
:::
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Blocks
|
||||||
|
````markdown
|
||||||
|
```python title="component_example.py"
|
||||||
|
from langflow.components.base import Component
|
||||||
|
|
||||||
|
class MyComponent(Component):
|
||||||
|
display_name = "My Component"
|
||||||
|
description = "Example component"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
return "Hello, World!"
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Images and Assets
|
||||||
|
```markdown
|
||||||
|
<!-- Images go in static/img/ -->
|
||||||
|

|
||||||
|
|
||||||
|
<!-- Use descriptive alt text -->
|
||||||
|

|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Component Documentation
|
||||||
|
|
||||||
|
### Component Page Template
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: Component Name
|
||||||
|
description: Brief description of what the component does
|
||||||
|
sidebar_position: 1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Component Name
|
||||||
|
|
||||||
|
Brief overview of the component's purpose.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
What this component does and when to use it.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
| Input | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `input_text` | String | Yes | The text to process |
|
||||||
|
| `model_name` | String | No | Model to use (default: gpt-3.5-turbo) |
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
|
||||||
|
| Output | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `result` | Message | Processed result |
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Example of using the component
|
||||||
|
component = MyComponent(
|
||||||
|
input_text="Hello, world!",
|
||||||
|
model_name="gpt-4"
|
||||||
|
)
|
||||||
|
result = component.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Issue: Component not loading
|
||||||
|
|
||||||
|
**Solution:** Check that all required inputs are provided.
|
||||||
|
|
||||||
|
### Issue: API key errors
|
||||||
|
|
||||||
|
**Solution:** Ensure your API key is properly configured.
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Documentation
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: API Endpoint
|
||||||
|
description: REST API endpoint documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
# API Endpoint Name
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
`POST /api/v1/endpoint`
|
||||||
|
|
||||||
|
## Request
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Authorization": "Bearer <token>",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Body
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"parameter": "value",
|
||||||
|
"optional_param": "optional_value"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response
|
||||||
|
|
||||||
|
### Success (200)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"result": "success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error (400)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "Error message"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:7860/api/v1/endpoint \
|
||||||
|
-H "Authorization: Bearer your-token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"parameter": "value"}'
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Blog Posts and Announcements
|
||||||
|
|
||||||
|
### Blog Post Template
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: "Release: Langflow v1.1.0"
|
||||||
|
description: "New features and improvements in Langflow v1.1.0"
|
||||||
|
authors: [author-name]
|
||||||
|
date: 2024-01-15
|
||||||
|
tags: [release, features]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Release: Langflow v1.1.0
|
||||||
|
|
||||||
|
Brief introduction to the release.
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
### Feature 1
|
||||||
|
Description of the feature and how to use it.
|
||||||
|
|
||||||
|
### Feature 2
|
||||||
|
Another feature description.
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
|
||||||
|
- List of improvements
|
||||||
|
- Bug fixes
|
||||||
|
- Performance enhancements
|
||||||
|
|
||||||
|
## Breaking Changes
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
List any breaking changes that require user action.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
Steps to migrate from previous versions.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Announcement Posts
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: "Announcement: New Integration"
|
||||||
|
description: "Langflow now supports integration with XYZ service"
|
||||||
|
authors: [author-name]
|
||||||
|
date: 2024-01-15
|
||||||
|
tags: [announcement, integration]
|
||||||
|
---
|
||||||
|
|
||||||
|
Brief announcement content with clear call-to-action.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Configuration
|
||||||
|
|
||||||
|
### Sidebar Configuration (`sidebars.js`)
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
docs: [
|
||||||
|
'introduction',
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Getting Started',
|
||||||
|
items: [
|
||||||
|
'getting-started/installation',
|
||||||
|
'getting-started/quickstart',
|
||||||
|
'getting-started/first-flow',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Components',
|
||||||
|
items: [
|
||||||
|
'components/overview',
|
||||||
|
'components/inputs',
|
||||||
|
'components/outputs',
|
||||||
|
'components/processing',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Site Configuration (`docusaurus.config.js`)
|
||||||
|
```javascript
|
||||||
|
module.exports = {
|
||||||
|
title: 'Langflow Documentation',
|
||||||
|
tagline: 'Build AI flows visually',
|
||||||
|
url: 'https://docs.langflow.org',
|
||||||
|
baseUrl: '/',
|
||||||
|
|
||||||
|
themeConfig: {
|
||||||
|
navbar: {
|
||||||
|
title: 'Langflow',
|
||||||
|
logo: {
|
||||||
|
alt: 'Langflow Logo',
|
||||||
|
src: 'img/logo.svg',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
docId: 'introduction',
|
||||||
|
position: 'left',
|
||||||
|
label: 'Docs',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: '/blog',
|
||||||
|
label: 'Blog',
|
||||||
|
position: 'left'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Documentation Testing
|
||||||
|
|
||||||
|
### Link Checking
|
||||||
|
```bash
|
||||||
|
# Check for broken internal links
|
||||||
|
yarn build
|
||||||
|
yarn serve
|
||||||
|
# Manual testing or use link checker tools
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Review
|
||||||
|
- **Accuracy:** Verify all code examples work
|
||||||
|
- **Completeness:** Ensure all features are documented
|
||||||
|
- **Clarity:** Review for clear, concise language
|
||||||
|
- **Navigation:** Test sidebar and cross-references
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
- Keep screenshots up-to-date with current UI
|
||||||
|
- Use consistent browser/OS for screenshots
|
||||||
|
- Highlight relevant UI elements
|
||||||
|
- Use descriptive file names
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Style Guide
|
||||||
|
|
||||||
|
### Writing Style
|
||||||
|
- **Tone:** Professional but approachable
|
||||||
|
- **Voice:** Second person ("you") for instructions
|
||||||
|
- **Tense:** Present tense for current features
|
||||||
|
- **Length:** Keep paragraphs short and scannable
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
- **Headers:** Use sentence case
|
||||||
|
- **Code:** Inline code with `backticks`
|
||||||
|
- **Emphasis:** Use **bold** for UI elements, *italic* for emphasis
|
||||||
|
- **Lists:** Use parallel structure
|
||||||
|
|
||||||
|
### Terminology
|
||||||
|
- **Langflow:** Always capitalize
|
||||||
|
- **Component:** Capitalize when referring to Langflow components
|
||||||
|
- **Flow:** Capitalize when referring to Langflow flows
|
||||||
|
- **API:** Always uppercase
|
||||||
|
- **JSON:** Always uppercase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Deployment
|
||||||
|
|
||||||
|
### Local Testing
|
||||||
|
```bash
|
||||||
|
yarn build # Build static site
|
||||||
|
yarn serve # Serve built site locally
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Deployment
|
||||||
|
- Documentation is automatically deployed on commit to main branch
|
||||||
|
- Build artifacts go to `build/` directory
|
||||||
|
- Static site is served via CDN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Development Checklist
|
||||||
|
- [ ] Documentation service running with `yarn start`
|
||||||
|
- [ ] Content follows markdown conventions
|
||||||
|
- [ ] Code examples are tested and working
|
||||||
|
- [ ] Images have descriptive alt text
|
||||||
|
- [ ] Internal links are functional
|
||||||
|
- [ ] Sidebar navigation is updated
|
||||||
|
- [ ] Content follows style guide
|
||||||
|
- [ ] Screenshots are current and properly formatted
|
||||||
|
- [ ] Cross-references between related topics
|
||||||
|
- [ ] Build succeeds with `yarn build`
|
||||||
374
.cursor/rules/frontend_development.mdc
Normal file
374
.cursor/rules/frontend_development.mdc
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
---
|
||||||
|
description: "Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing."
|
||||||
|
globs:
|
||||||
|
- "src/frontend/**/*.{ts,tsx,js,jsx}"
|
||||||
|
- "src/frontend/**/*.{css,scss,json}"
|
||||||
|
- "src/frontend/package*.json"
|
||||||
|
- "src/frontend/vite.config.*"
|
||||||
|
- "src/frontend/tailwind.config.*"
|
||||||
|
- "src/frontend/tsconfig.json"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Frontend Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- **Node.js:** v22.12 LTS for JavaScript runtime
|
||||||
|
- **Package Manager:** npm (v10.9) for dependency management
|
||||||
|
- **Development Tools:** Vite for build tooling
|
||||||
|
|
||||||
|
### Frontend Service
|
||||||
|
```bash
|
||||||
|
make frontend # Start Vite dev server on port 3000
|
||||||
|
```
|
||||||
|
- Hot-reload enabled for UI changes
|
||||||
|
- Access at: http://localhost:3000/
|
||||||
|
- Frontend source: `src/frontend/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Frontend Structure
|
||||||
|
|
||||||
|
### Directory Layout
|
||||||
|
```
|
||||||
|
src/frontend/src/
|
||||||
|
├── components/ # Reusable UI components
|
||||||
|
├── pages/ # Page-level components
|
||||||
|
├── icons/ # Component icons and lazy loading
|
||||||
|
├── stores/ # State management (Zustand)
|
||||||
|
├── types/ # TypeScript type definitions
|
||||||
|
├── utils/ # Utility functions
|
||||||
|
├── hooks/ # Custom React hooks
|
||||||
|
├── services/ # API service functions
|
||||||
|
└── assets/ # Static assets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Technologies
|
||||||
|
- **React 18** with TypeScript
|
||||||
|
- **Vite** for build tooling and dev server
|
||||||
|
- **Tailwind CSS** for styling
|
||||||
|
- **Zustand** for state management
|
||||||
|
- **React Flow** for flow graph visualization
|
||||||
|
- **Lucide React** for icons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Frontend Code Quality
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
```bash
|
||||||
|
make format_frontend # Format TypeScript/JavaScript code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
```bash
|
||||||
|
make lint # Run ESLint and TypeScript checks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
make tests_frontend # Run frontend tests (requires additional setup)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pre-commit Workflow
|
||||||
|
1. Run `make format_frontend`
|
||||||
|
2. Run `make lint`
|
||||||
|
3. Test changes in browser
|
||||||
|
4. Commit changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. State Management
|
||||||
|
|
||||||
|
### Zustand Stores
|
||||||
|
```typescript
|
||||||
|
// stores/myStore.ts
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface MyState {
|
||||||
|
value: string;
|
||||||
|
setValue: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMyStore = create<MyState>((set) => ({
|
||||||
|
value: '',
|
||||||
|
setValue: (value) => set({ value }),
|
||||||
|
}));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Stores in Components
|
||||||
|
```typescript
|
||||||
|
// components/MyComponent.tsx
|
||||||
|
import { useMyStore } from '@/stores/myStore';
|
||||||
|
|
||||||
|
export function MyComponent() {
|
||||||
|
const { value, setValue } = useMyStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => setValue(e.target.value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API Integration
|
||||||
|
|
||||||
|
### Service Functions
|
||||||
|
```typescript
|
||||||
|
// services/api.ts
|
||||||
|
import { api } from '@/controllers/API';
|
||||||
|
|
||||||
|
export async function createFlow(flowData: FlowData) {
|
||||||
|
const response = await api.post('/flows/', flowData);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFlows() {
|
||||||
|
const response = await api.get('/flows/');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
```typescript
|
||||||
|
// hooks/useApi.ts
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
|
||||||
|
export function useApi<T>(apiFunction: (...args: any[]) => Promise<T>) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const execute = useCallback(async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const result = await apiFunction(...args);
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [apiFunction]);
|
||||||
|
|
||||||
|
return { execute, loading, error };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. React Flow Integration
|
||||||
|
|
||||||
|
### Flow Graph Components
|
||||||
|
```typescript
|
||||||
|
// components/FlowGraph.tsx
|
||||||
|
import ReactFlow, {
|
||||||
|
Node,
|
||||||
|
Edge,
|
||||||
|
Controls,
|
||||||
|
Background
|
||||||
|
} from 'reactflow';
|
||||||
|
|
||||||
|
interface FlowGraphProps {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
onNodesChange: (changes: NodeChange[]) => void;
|
||||||
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FlowGraph({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
onNodesChange,
|
||||||
|
onEdgesChange
|
||||||
|
}: FlowGraphProps) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full">
|
||||||
|
<ReactFlow
|
||||||
|
nodes={nodes}
|
||||||
|
edges={edges}
|
||||||
|
onNodesChange={onNodesChange}
|
||||||
|
onEdgesChange={onEdgesChange}
|
||||||
|
fitView
|
||||||
|
>
|
||||||
|
<Controls />
|
||||||
|
<Background />
|
||||||
|
</ReactFlow>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Node Types
|
||||||
|
```typescript
|
||||||
|
// components/nodes/ComponentNode.tsx
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Handle, Position } from 'reactflow';
|
||||||
|
|
||||||
|
interface ComponentNodeProps {
|
||||||
|
data: {
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ComponentNode = memo(({ data }: ComponentNodeProps) => {
|
||||||
|
return (
|
||||||
|
<div className="px-4 py-2 shadow-md rounded-md bg-white border">
|
||||||
|
<Handle type="target" position={Position.Top} />
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
{data.icon && (
|
||||||
|
<img src={data.icon} alt="" className="w-4 h-4 mr-2" />
|
||||||
|
)}
|
||||||
|
<span className="text-sm">{data.label}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Handle type="source" position={Position.Bottom} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Styling with Tailwind
|
||||||
|
|
||||||
|
### Component Styling
|
||||||
|
```typescript
|
||||||
|
// components/Button.tsx
|
||||||
|
import { cn } from '@/utils/cn';
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
variant?: 'primary' | 'secondary';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Button({
|
||||||
|
variant = 'primary',
|
||||||
|
size = 'md',
|
||||||
|
children,
|
||||||
|
onClick
|
||||||
|
}: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'rounded-md font-medium transition-colors',
|
||||||
|
{
|
||||||
|
'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
|
||||||
|
'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
|
||||||
|
'px-2 py-1 text-sm': size === 'sm',
|
||||||
|
'px-4 py-2 text-base': size === 'md',
|
||||||
|
'px-6 py-3 text-lg': size === 'lg',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dark Mode Support
|
||||||
|
```typescript
|
||||||
|
// hooks/useDarkMode.ts
|
||||||
|
import { useDarkStore } from '@/stores/darkStore';
|
||||||
|
|
||||||
|
export function useDarkMode() {
|
||||||
|
const { dark, setDark } = useDarkStore();
|
||||||
|
|
||||||
|
const toggle = () => setDark(!dark);
|
||||||
|
|
||||||
|
return { isDark: dark, toggle };
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Frontend Testing
|
||||||
|
|
||||||
|
### Component Testing
|
||||||
|
```typescript
|
||||||
|
// __tests__/Button.test.tsx
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
|
||||||
|
describe('Button', () => {
|
||||||
|
it('renders with correct text', () => {
|
||||||
|
render(<Button>Click me</Button>);
|
||||||
|
expect(screen.getByText('Click me')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onClick when clicked', () => {
|
||||||
|
const handleClick = jest.fn();
|
||||||
|
render(<Button onClick={handleClick}>Click me</Button>);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Click me'));
|
||||||
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
```typescript
|
||||||
|
// __tests__/FlowEditor.test.tsx
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { FlowEditor } from '@/pages/FlowEditor';
|
||||||
|
|
||||||
|
describe('FlowEditor', () => {
|
||||||
|
it('loads flow data correctly', async () => {
|
||||||
|
render(<FlowEditor flowId="test-flow-id" />);
|
||||||
|
|
||||||
|
// Wait for flow to load
|
||||||
|
await screen.findByText('Flow loaded');
|
||||||
|
|
||||||
|
expect(screen.getByTestId('flow-canvas')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Build and Deployment
|
||||||
|
|
||||||
|
### Development Build
|
||||||
|
```bash
|
||||||
|
make build_frontend # Build frontend static files
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Build
|
||||||
|
```bash
|
||||||
|
cd src/frontend
|
||||||
|
npm run build # Creates dist/ directory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Integration
|
||||||
|
- Frontend builds to `src/frontend/dist/`
|
||||||
|
- Backend serves static files from this directory in production
|
||||||
|
- `make init` includes frontend build step
|
||||||
|
---
|
||||||
|
|
||||||
|
## Frontend Development Checklist
|
||||||
|
- [ ] Frontend service running with `make frontend`
|
||||||
|
- [ ] Changes hot-reload correctly in browser
|
||||||
|
- [ ] State management uses Zustand stores
|
||||||
|
- [ ] API calls use proper error handling
|
||||||
|
- [ ] Components styled with Tailwind CSS
|
||||||
|
- [ ] Dark mode support implemented where needed
|
||||||
|
- [ ] Code formatted with `make format_frontend`
|
||||||
|
- [ ] Linting passed with `make lint`
|
||||||
|
- [ ] Changes tested in both light and dark mode
|
||||||
|
|
||||||
|
- [ ] Components styled with Tailwind CSS
|
||||||
|
- [ ] Dark mode support implemented where needed
|
||||||
|
- [ ] Code formatted with `make format_frontend`
|
||||||
|
- [ ] Changes tested in both light and dark mode
|
||||||
|
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
---
|
---
|
||||||
description: Rules and checklist for adding and using langflow component icons.
|
description: "Rules for creating and implementing component icons in Langflow, covering both backend Python component icon attributes and frontend React icon implementation."
|
||||||
globs:
|
globs:
|
||||||
|
- "src/frontend/src/icons/**/*"
|
||||||
|
- "src/frontend/src/icons/lazyIconImports.ts"
|
||||||
|
- "src/backend/**/*component*.py"
|
||||||
|
- "src/backend/**/components/**/*.py"
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
# Component Icon Rules
|
# Component Icon Rules
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
|
||||||
561
.cursor/rules/testing.mdc
Normal file
561
.cursor/rules/testing.mdc
Normal file
|
|
@ -0,0 +1,561 @@
|
||||||
|
---
|
||||||
|
description: "Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices."
|
||||||
|
globs:
|
||||||
|
- "src/backend/tests/**/*.py"
|
||||||
|
- "src/frontend/**/*.test.{ts,tsx,js,jsx}"
|
||||||
|
- "src/frontend/**/*.spec.{ts,tsx,js,jsx}"
|
||||||
|
- "tests/**/*.py"
|
||||||
|
- "conftest.py"
|
||||||
|
- "pytest.ini"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Guidelines for Langflow
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Guidelines for testing Langflow components and backend code, with emphasis on async patterns and robust, well-documented testing practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Testing Structure
|
||||||
|
|
||||||
|
### Backend Tests Location
|
||||||
|
- **Unit Tests:** `src/backend/tests/`
|
||||||
|
- **Component Tests:** `src/backend/tests/unit/components/` (organized by component subdirectory)
|
||||||
|
- **Integration Tests:** Available via `make integration_tests` (requires additional setup)
|
||||||
|
|
||||||
|
### Test File Naming
|
||||||
|
- Use same filename as component with appropriate test prefix/suffix
|
||||||
|
- Example: `my_component.py` → `test_my_component.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Built-in Fixtures & Base Classes
|
||||||
|
|
||||||
|
### `client` Fixture (FastAPI Test Client)
|
||||||
|
- Defined in `src/backend/tests/conftest.py`
|
||||||
|
- Provides an **async** `httpx.AsyncClient` connected to the full application via `ASGITransport` + `LifespanManager`.
|
||||||
|
- Use it for API tests:
|
||||||
|
```python
|
||||||
|
async def test_login_endpoint(client):
|
||||||
|
response = await client.post("api/v1/login", data={"username": "foo", "password": "bar"})
|
||||||
|
assert response.status_code == 200
|
||||||
|
```
|
||||||
|
- Automatically configured with an **in-memory SQLite database** and mocked environment variables. No additional setup needed in individual tests.
|
||||||
|
- Skip client creation by marking the test with `@pytest.mark.noclient`.
|
||||||
|
|
||||||
|
### `ComponentTestBase` Family
|
||||||
|
Located in `src/backend/tests/base.py`.
|
||||||
|
|
||||||
|
| Base Class | Creates `client`? | Typical Use | Notes |
|
||||||
|
|------------|------------------|-------------|-------|
|
||||||
|
| `ComponentTestBase` | No | Core logic for component version testing | Requires you to provide fixtures described below. |
|
||||||
|
| `ComponentTestBaseWithClient` | Yes (`@pytest.mark.usefixtures("client")`) | Components that need API access during `run()` | Inherit when the component interacts with backend services. |
|
||||||
|
| `ComponentTestBaseWithoutClient` | No | Pure-logic components with no API calls | Lightweight alternative. |
|
||||||
|
|
||||||
|
Required fixtures for subclasses:
|
||||||
|
1. `component_class` → the component **class** under test.
|
||||||
|
2. `default_kwargs` → dict of kwargs to instantiate the component (can be empty).
|
||||||
|
3. `file_names_mapping` → list of `VersionComponentMapping` dicts mapping historical **Langflow versions** to module/file names.
|
||||||
|
|
||||||
|
Example skeleton:
|
||||||
|
```python
|
||||||
|
from tests.base import ComponentTestBaseWithClient, VersionComponentMapping, DID_NOT_EXIST
|
||||||
|
from langflow.components.my_namespace import MyComponent
|
||||||
|
|
||||||
|
class TestMyComponent(ComponentTestBaseWithClient):
|
||||||
|
@pytest.fixture
|
||||||
|
def component_class(self):
|
||||||
|
return MyComponent
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def default_kwargs(self):
|
||||||
|
return {"foo": "bar"}
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def file_names_mapping(self):
|
||||||
|
return [
|
||||||
|
VersionComponentMapping(version="1.1.1", module="my_module", file_name="my_component.py"),
|
||||||
|
# Older versions can be mapped or DID_NOT_EXIST
|
||||||
|
VersionComponentMapping(version="1.0.19", module="my_module", file_name=DID_NOT_EXIST),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
`ComponentTestBase` automatically provides:
|
||||||
|
- `test_latest_version` → Instantiates component via `component_class` and asserts `run()` doesn't return `None`.
|
||||||
|
- `test_all_versions_have_a_file_name_defined` → Ensures mapping completeness vs `SUPPORTED_VERSIONS` constant (`src/backend/tests/constants.py`).
|
||||||
|
- `test_component_versions` (parametrised) → Builds component from source for each supported version and asserts successful execution.
|
||||||
|
|
||||||
|
When adding a new component test, **inherit from the correct base class and provide the three fixtures**. This greatly reduces boilerplate and enforces version compatibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Component Testing Requirements
|
||||||
|
|
||||||
|
### Minimum Testing Requirements
|
||||||
|
- **Unit Tests:** Create comprehensive unit tests for all new components
|
||||||
|
- **Manual Test Documentation:** If unit tests are incomplete, create a Markdown file with manual testing steps
|
||||||
|
- Location: Same directory as unit tests
|
||||||
|
- Filename: Same as component but with `.md` extension
|
||||||
|
- Content: Detailed manual testing steps and expected outcomes
|
||||||
|
|
||||||
|
### Testing Best Practices
|
||||||
|
- Test both sync and async code paths
|
||||||
|
- Mock external dependencies appropriately
|
||||||
|
- Test error handling and edge cases
|
||||||
|
- Validate input/output behavior
|
||||||
|
- Test component initialization and configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Async Testing Patterns
|
||||||
|
|
||||||
|
### Async Component Testing
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_async_component():
|
||||||
|
# Test async component methods
|
||||||
|
result = await component.async_method()
|
||||||
|
assert result is not None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Background Tasks
|
||||||
|
```python
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_background_task_completion():
|
||||||
|
# Ensure background tasks complete properly
|
||||||
|
task = asyncio.create_task(component.background_operation())
|
||||||
|
result = await asyncio.wait_for(task, timeout=5.0)
|
||||||
|
assert result.success
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Queue Operations
|
||||||
|
```python
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_queue_operations():
|
||||||
|
# Test queue put/get operations without blocking
|
||||||
|
queue = asyncio.Queue()
|
||||||
|
|
||||||
|
# Non-blocking put
|
||||||
|
queue.put_nowait(test_data)
|
||||||
|
|
||||||
|
# Verify queue processing
|
||||||
|
result = await asyncio.wait_for(queue.get(), timeout=1.0)
|
||||||
|
assert result == test_data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Special Testing Considerations
|
||||||
|
|
||||||
|
### Blockbuster Plugin
|
||||||
|
- Use `no_blockbuster` pytest marker to skip blockbuster plugin in tests
|
||||||
|
- Example: `@pytest.mark.no_blockbuster`
|
||||||
|
|
||||||
|
### Database Tests
|
||||||
|
- `test_database.py` may fail in batch runs but pass individually
|
||||||
|
- Run problematic tests sequentially: `uv run pytest src/backend/tests/unit/test_database.py`
|
||||||
|
- Consider this behavior when writing database-related tests
|
||||||
|
|
||||||
|
### Context Variables in Async Tests
|
||||||
|
- Be aware of ContextVar propagation in async tests
|
||||||
|
- Test both direct event loop execution and `asyncio.to_thread` scenarios
|
||||||
|
- Ensure proper context isolation between test cases
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Test Execution
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
```bash
|
||||||
|
make unit_tests # Run all backend unit tests
|
||||||
|
make integration_tests # Run integration tests (requires additional setup)
|
||||||
|
make coverage # Run tests with coverage (requires additional setup)
|
||||||
|
make tests_frontend # Run frontend tests (requires additional setup)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Individual Test Execution
|
||||||
|
```bash
|
||||||
|
# Run specific test file
|
||||||
|
uv run pytest src/backend/tests/unit/test_specific_component.py
|
||||||
|
|
||||||
|
# Run specific test method
|
||||||
|
uv run pytest src/backend/tests/unit/test_component.py::test_specific_method
|
||||||
|
|
||||||
|
# Run with verbose output
|
||||||
|
uv run pytest -v src/backend/tests/unit/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Robust Testing Patterns
|
||||||
|
|
||||||
|
### Error Handling Tests
|
||||||
|
```python
|
||||||
|
def test_component_error_handling():
|
||||||
|
"""Test that component handles errors gracefully."""
|
||||||
|
with pytest.raises(ExpectedError):
|
||||||
|
component.method_that_should_fail(invalid_input)
|
||||||
|
|
||||||
|
# Test error message quality
|
||||||
|
try:
|
||||||
|
component.risky_operation()
|
||||||
|
except ComponentError as e:
|
||||||
|
assert "helpful error message" in str(e)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resource Cleanup Tests
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
async def component_with_cleanup():
|
||||||
|
"""Fixture ensuring proper resource cleanup."""
|
||||||
|
component = MyComponent()
|
||||||
|
await component.initialize()
|
||||||
|
try:
|
||||||
|
yield component
|
||||||
|
finally:
|
||||||
|
await component.cleanup()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Timeout and Performance Tests
|
||||||
|
```python
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_operation_timeout():
|
||||||
|
"""Test that operations respect timeout constraints."""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
with pytest.raises(asyncio.TimeoutError):
|
||||||
|
await asyncio.wait_for(
|
||||||
|
component.long_running_operation(),
|
||||||
|
timeout=1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
assert elapsed < 1.5 # Allow some tolerance
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Documentation in Tests
|
||||||
|
|
||||||
|
### Test Documentation Standards
|
||||||
|
- Each test should have a clear docstring explaining its purpose
|
||||||
|
- Complex test setups should be commented
|
||||||
|
- Mock usage should be documented
|
||||||
|
- Expected behaviors should be explicitly stated
|
||||||
|
|
||||||
|
### Example Well-Documented Test
|
||||||
|
```python
|
||||||
|
async def test_component_background_processing():
|
||||||
|
"""
|
||||||
|
Test that component processes items in background without blocking.
|
||||||
|
|
||||||
|
This test verifies:
|
||||||
|
1. Items are added to processing queue immediately
|
||||||
|
2. Background processing completes successfully
|
||||||
|
3. No deadlocks occur during shutdown
|
||||||
|
4. All tasks are properly cleaned up
|
||||||
|
"""
|
||||||
|
# Setup component with background processing
|
||||||
|
component = BackgroundComponent()
|
||||||
|
await component.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Add items for processing
|
||||||
|
for i in range(10):
|
||||||
|
await component.add_item(f"item_{i}")
|
||||||
|
|
||||||
|
# Verify items are queued (non-blocking)
|
||||||
|
assert component.queue_size() == 10
|
||||||
|
|
||||||
|
# Wait for processing to complete
|
||||||
|
await component.wait_for_completion(timeout=5.0)
|
||||||
|
|
||||||
|
# Verify all items processed
|
||||||
|
assert component.processed_count() == 10
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Ensure clean shutdown
|
||||||
|
await component.stop()
|
||||||
|
assert component.is_stopped()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
- [ ] Unit tests created for all new components
|
||||||
|
- [ ] Async patterns tested appropriately
|
||||||
|
- [ ] Error handling and edge cases covered
|
||||||
|
- [ ] Manual testing documentation created (if unit tests incomplete)
|
||||||
|
- [ ] Background tasks and queues tested for proper cleanup
|
||||||
|
- [ ] Database tests considered for sequential execution if needed
|
||||||
|
- [ ] Appropriate pytest markers used (`no_blockbuster`, etc.)
|
||||||
|
- [ ] Tests are well-documented with clear docstrings
|
||||||
|
- [ ] Resource cleanup properly handled in fixtures
|
||||||
|
- [ ] Timeout and performance constraints validated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Langflow-Specific Testing Patterns
|
||||||
|
|
||||||
|
### Message Testing
|
||||||
|
Test Langflow's `Message` objects and chat functionality:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from langflow.schema.message import Message
|
||||||
|
from langflow.utils.constants import MESSAGE_SENDER_AI, MESSAGE_SENDER_USER
|
||||||
|
|
||||||
|
async def test_message_response(self, component_class, default_kwargs):
|
||||||
|
"""Test Message object creation and properties."""
|
||||||
|
component = component_class(**default_kwargs)
|
||||||
|
message = await component.message_response()
|
||||||
|
|
||||||
|
assert isinstance(message, Message)
|
||||||
|
assert message.text == default_kwargs["input_value"]
|
||||||
|
assert message.sender == default_kwargs["sender"]
|
||||||
|
assert message.sender_name == default_kwargs["sender_name"]
|
||||||
|
assert message.session_id == default_kwargs["session_id"]
|
||||||
|
assert message.files == default_kwargs["files"]
|
||||||
|
# Test message properties structure
|
||||||
|
assert message.properties.model_dump() == {
|
||||||
|
"background_color": default_kwargs["background_color"],
|
||||||
|
"text_color": default_kwargs["text_color"],
|
||||||
|
"icon": default_kwargs["chat_icon"],
|
||||||
|
# ... other expected properties
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow Testing with JSON Data
|
||||||
|
Use predefined JSON flows and utility functions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from tests.unit.build_utils import create_flow, build_flow, get_build_events, consume_and_assert_stream
|
||||||
|
|
||||||
|
async def test_flow_execution(client, json_memory_chatbot_no_llm, logged_in_headers):
|
||||||
|
"""Test flow creation, building, and execution."""
|
||||||
|
# Create flow from JSON data
|
||||||
|
flow_id = await create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
|
||||||
|
|
||||||
|
# Start the build process
|
||||||
|
build_response = await build_flow(client, flow_id, logged_in_headers)
|
||||||
|
job_id = build_response["job_id"]
|
||||||
|
|
||||||
|
# Get and validate event stream
|
||||||
|
events_response = await get_build_events(client, job_id, logged_in_headers)
|
||||||
|
assert events_response.status_code == codes.OK
|
||||||
|
|
||||||
|
# Process events and validate structure
|
||||||
|
await consume_and_assert_stream(events_response, job_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Testing with Real Services
|
||||||
|
Test components that need external APIs with proper markers:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.mark.api_key_required
|
||||||
|
@pytest.mark.no_blockbuster
|
||||||
|
async def test_component_with_external_api(self):
|
||||||
|
"""Test component with external API integration."""
|
||||||
|
api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
component = MyAPIComponent(
|
||||||
|
api_key=api_key,
|
||||||
|
model_name="gpt-4o",
|
||||||
|
input_value="test input",
|
||||||
|
session_id=str(uuid4()),
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await component.message_response()
|
||||||
|
assert response.data.get("text") is not None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mocking Language Models
|
||||||
|
Use `MockLanguageModel` for testing without external APIs:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from tests.unit.mock_language_model import MockLanguageModel
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def default_kwargs(self):
|
||||||
|
return {
|
||||||
|
"agent_llm": MockLanguageModel(),
|
||||||
|
"input_value": "test message",
|
||||||
|
"session_id": str(uuid4()),
|
||||||
|
# ... other component-specific kwargs
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Handling in Tests
|
||||||
|
Use `anyio` and `aiofiles` for async file operations:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import aiofiles
|
||||||
|
import anyio
|
||||||
|
|
||||||
|
async def test_file_operations(self, component_class, tmp_path):
|
||||||
|
"""Test component file handling."""
|
||||||
|
# Create temporary test file
|
||||||
|
test_file = anyio.Path(tmp_path) / "test.txt"
|
||||||
|
await test_file.write_text("Test content", encoding="utf-8")
|
||||||
|
|
||||||
|
# Test with file input
|
||||||
|
component = component_class(files=[str(test_file)])
|
||||||
|
result = await component.run()
|
||||||
|
|
||||||
|
# Verify file was processed
|
||||||
|
assert len(result.files) == 1
|
||||||
|
assert await test_file.exists()
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoint Testing
|
||||||
|
Test Langflow's REST API endpoints:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def test_flows_endpoint(client, logged_in_headers):
|
||||||
|
"""Test flow creation via API."""
|
||||||
|
flow_data = {
|
||||||
|
"name": "Test Flow",
|
||||||
|
"description": "Test description",
|
||||||
|
"data": {"nodes": [], "edges": []}
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await client.post(
|
||||||
|
"api/v1/flows/",
|
||||||
|
json=flow_data,
|
||||||
|
headers=logged_in_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == codes.CREATED
|
||||||
|
|
||||||
|
flow = response.json()
|
||||||
|
assert flow["name"] == "Test Flow"
|
||||||
|
assert "id" in flow
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Config Testing
|
||||||
|
Test component configuration updates:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def test_build_config_updates(self, component_class, default_kwargs):
|
||||||
|
"""Test dynamic build configuration updates."""
|
||||||
|
component = await self.component_setup(component_class, default_kwargs)
|
||||||
|
frontend_node = component.to_frontend_node()
|
||||||
|
build_config = frontend_node["data"]["node"]["template"]
|
||||||
|
|
||||||
|
# Test configuration update
|
||||||
|
component.set(llm_provider="OpenAI")
|
||||||
|
updated_config = await component.update_build_config(
|
||||||
|
build_config, "OpenAI", "llm_provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert updated_config["llm_provider"]["value"] == "OpenAI"
|
||||||
|
assert "gpt-4o" in updated_config["model_name"]["options"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Event Streams
|
||||||
|
Test real-time event streaming endpoints:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def consume_and_validate_events(response, expected_job_id):
|
||||||
|
"""Consume NDJSON event stream and validate structure."""
|
||||||
|
count = 0
|
||||||
|
first_event_seen = False
|
||||||
|
end_event_seen = False
|
||||||
|
|
||||||
|
async for line in response.aiter_lines():
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
parsed = json.loads(line)
|
||||||
|
|
||||||
|
# Validate job_id in events
|
||||||
|
if "job_id" in parsed:
|
||||||
|
assert parsed["job_id"] == expected_job_id
|
||||||
|
|
||||||
|
# First event should be vertices_sorted
|
||||||
|
if not first_event_seen:
|
||||||
|
assert parsed["event"] == "vertices_sorted"
|
||||||
|
first_event_seen = True
|
||||||
|
|
||||||
|
# Track end event
|
||||||
|
elif parsed["event"] == "end":
|
||||||
|
end_event_seen = True
|
||||||
|
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
assert first_event_seen and end_event_seen
|
||||||
|
return count
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Component Versioning
|
||||||
|
Test backward compatibility across Langflow versions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest.fixture
|
||||||
|
def file_names_mapping(self):
|
||||||
|
"""Map component files across Langflow versions."""
|
||||||
|
return [
|
||||||
|
VersionComponentMapping(
|
||||||
|
version="1.1.1",
|
||||||
|
module="inputs",
|
||||||
|
file_name="chat"
|
||||||
|
),
|
||||||
|
VersionComponentMapping(
|
||||||
|
version="1.1.0",
|
||||||
|
module="inputs",
|
||||||
|
file_name="chat"
|
||||||
|
),
|
||||||
|
VersionComponentMapping(
|
||||||
|
version="1.0.19",
|
||||||
|
module="inputs",
|
||||||
|
file_name="ChatInput"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Webhook Testing
|
||||||
|
Test webhook endpoints with proper payload handling:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def test_webhook_endpoint(client, added_webhook_test):
|
||||||
|
"""Test webhook flow execution."""
|
||||||
|
endpoint_name = added_webhook_test["endpoint_name"]
|
||||||
|
endpoint = f"api/v1/webhook/{endpoint_name}"
|
||||||
|
|
||||||
|
payload = {"path": "/tmp/test_file.txt"}
|
||||||
|
|
||||||
|
response = await client.post(endpoint, json=payload)
|
||||||
|
assert response.status_code == 202
|
||||||
|
|
||||||
|
# Verify webhook processed the payload
|
||||||
|
# ... additional validation logic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monkeypatch for Testing Errors
|
||||||
|
Test error handling by mocking internal functions:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def test_cancellation_error(client, flow_id, logged_in_headers, monkeypatch):
|
||||||
|
"""Test error handling during flow cancellation."""
|
||||||
|
import langflow.api.v1.chat
|
||||||
|
|
||||||
|
async def mock_cancel_with_error(*args, **kwargs):
|
||||||
|
raise RuntimeError("Cancellation failed")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
langflow.api.v1.chat,
|
||||||
|
"cancel_flow_build",
|
||||||
|
mock_cancel_with_error
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.post(
|
||||||
|
f"api/v1/build/{job_id}/cancel",
|
||||||
|
headers=logged_in_headers
|
||||||
|
)
|
||||||
|
assert response.status_code == codes.INTERNAL_SERVER_ERROR
|
||||||
|
assert "Cancellation failed" in response.json()["detail"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue