From ab1ed8ea017566737ab227f37946b4e1831117c8 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 12 Jun 2025 09:51:18 -0300 Subject: [PATCH] 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> --- .cursor/rules/backend_development.mdc | 268 +++++++++ .cursor/rules/components/basic_component.mdc | 8 +- .cursor/rules/docs_development.mdc | 440 +++++++++++++++ .cursor/rules/frontend_development.mdc | 374 +++++++++++++ .cursor/rules/icons.mdc | 36 +- .cursor/rules/testing.mdc | 561 +++++++++++++++++++ 6 files changed, 1670 insertions(+), 17 deletions(-) create mode 100644 .cursor/rules/backend_development.mdc create mode 100644 .cursor/rules/docs_development.mdc create mode 100644 .cursor/rules/frontend_development.mdc create mode 100644 .cursor/rules/testing.mdc diff --git a/.cursor/rules/backend_development.mdc b/.cursor/rules/backend_development.mdc new file mode 100644 index 000000000..3ed0261f9 --- /dev/null +++ b/.cursor/rules/backend_development.mdc @@ -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 diff --git a/.cursor/rules/components/basic_component.mdc b/.cursor/rules/components/basic_component.mdc index 98fcb60db..f2cb445de 100644 --- a/.cursor/rules/components/basic_component.mdc +++ b/.cursor/rules/components/basic_component.mdc @@ -1,8 +1,12 @@ --- -description: Rules and checklist for creating a basic Langflow Component -globs: +description: "Guide for creating a basic Langflow component, including requirements gathering, component structure, and implementation best practices." +globs: + - "src/backend/**/components/**/*.py" + - "src/backend/base/langflow/components/**/*.py" alwaysApply: false --- + + # Rule: How to Create a Basic Langflow Component ## Purpose diff --git a/.cursor/rules/docs_development.mdc b/.cursor/rules/docs_development.mdc new file mode 100644 index 000000000..6e694f01b --- /dev/null +++ b/.cursor/rules/docs_development.mdc @@ -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 + +![Component Overview](/img/components/overview.png) + + +![Langflow interface showing the flow editor with nodes and connections](/img/flow-editor.png) +``` + +--- + +## 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 ", + "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` diff --git a/.cursor/rules/frontend_development.mdc b/.cursor/rules/frontend_development.mdc new file mode 100644 index 000000000..42b3dac7e --- /dev/null +++ b/.cursor/rules/frontend_development.mdc @@ -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((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 ( + 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(apiFunction: (...args: any[]) => Promise) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(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 ( +
+ + + + +
+ ); +} +``` + +### 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 ( +
+ + +
+ {data.icon && ( + + )} + {data.label} +
+ + +
+ ); +}); +``` + +--- + +## 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 ( + + ); +} +``` + +### 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(); + expect(screen.getByText('Click me')).toBeInTheDocument(); + }); + + it('calls onClick when clicked', () => { + const handleClick = jest.fn(); + render(); + + 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(); + + // 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 + diff --git a/.cursor/rules/icons.mdc b/.cursor/rules/icons.mdc index 5d46a2b8f..30a7550dd 100644 --- a/.cursor/rules/icons.mdc +++ b/.cursor/rules/icons.mdc @@ -1,8 +1,14 @@ --- -description: Rules and checklist for adding and using langflow component icons. -globs: +description: "Rules for creating and implementing component icons in Langflow, covering both backend Python component icon attributes and frontend React icon implementation." +globs: + - "src/frontend/src/icons/**/*" + - "src/frontend/src/icons/lazyIconImports.ts" + - "src/backend/**/*component*.py" + - "src/backend/**/components/**/*.py" alwaysApply: false --- + + # Component Icon Rules ## Purpose @@ -13,12 +19,12 @@ To ensure consistent, clear, and functional icon usage for components, covering ## 1. Backend (Python) — Setting the Icon Name - **Where:** In your component class (e.g., in `src/backend/base/langflow/components/vectorstores/astradb.py`) -- **How:** +- **How:** Set the `icon` attribute to a string matching the icon you want to use. ```python icon = "AstraDB" ``` -- **Tip:** +- **Tip:** The string must match the frontend icon mapping exactly (case-sensitive). --- @@ -27,9 +33,9 @@ To ensure consistent, clear, and functional icon usage for components, covering ### a. Create the Icon Component -- **Where:** +- **Where:** In a new directory for your icon, e.g., `src/frontend/src/icons/AstraDB/`. -- **How:** +- **How:** - Add your SVG as a React component, e.g., `AstraSVG` in `AstraDB.jsx`. ```jsx const AstraSVG = (props) => ( @@ -74,28 +80,28 @@ To ensure consistent, clear, and functional icon usage for components, covering ### b. Add to Lazy Icon Imports -- **Where:** +- **Where:** In `src/frontend/src/icons/lazyIconImports.ts` -- **How:** +- **How:** Add an entry to the `lazyIconsMapping` object: ```ts AstraDB: () => import("@/icons/AstraDB").then((mod) => ({ default: mod.AstraDBIcon })), ``` -- **Tip:** +- **Tip:** The key (`AstraDB`) must match the string used in the backend. --- ## 3. Best Practices -- **Naming:** +- **Naming:** Use clear, recognizable names (e.g., `"AstraDB"`, `"Postgres"`, `"OpenAI"`). -- **Consistency:** +- **Consistency:** Always use the same icon name for the same service across backend and frontend. -- **Missing Icon:** +- **Missing Icon:** If no icon exists, use a [lucide icon](https://lucide.dev/icons) -- **Light/Dark Mode:** +- **Light/Dark Mode:** Always support both light and dark mode for custom icons by using the `isdark` prop in your SVG. --- @@ -114,11 +120,11 @@ To ensure consistent, clear, and functional icon usage for components, covering --- **Example for AstraDB:** -- Backend: +- Backend: ```python icon = "AstraDB" ``` -- Frontend: +- Frontend: - `src/icons/AstraDB/AstraDB.jsx` (SVG as React component, uses `isdark` prop) - `src/icons/AstraDB/index.tsx` (exports `AstraDBIcon` and passes `isdark`) - Add to `lazyIconImports.ts`: diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 000000000..1bd0af1cf --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -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"] +``` + +