Spaces:
Sleeping
Sleeping
File size: 17,511 Bytes
018ab56 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 | # Development Guide
This document provides a comprehensive guide for setting up a local development environment, understanding the project structure, and extending the Visual Notebook with new operations, chart types, and frontend components.
## Prerequisites
Before setting up the development environment, ensure you have the following tools installed:
| Tool | Version | Purpose |
|------|---------|---------|
| Python | 3.11+ | Backend runtime |
| Node.js | 20+ | Frontend build and development |
| npm | 10+ | Frontend package management |
| uv | Latest | Python package management (replaces pip/venv) |
| Git | 2.40+ | Version control |
### Installing uv
```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
```
## Local Development Setup
### Backend Setup
1. **Navigate to the project root**:
```bash
cd /path/to/va_project
```
2. **Create a virtual environment and install dependencies**:
```bash
uv sync
```
This command reads `pyproject.toml`, resolves dependencies, creates a virtual environment in `.venv/`, and installs all packages including development dependencies (pytest, httpx).
3. **Run the backend server**:
```bash
uv run uvicorn visual_notebook.main:app --reload --host 0.0.0.0 --port 8000
```
The `--reload` flag enables automatic server restart when source files change.
4. **Verify the server is running**:
```bash
curl http://localhost:8000/health
# Expected: {"status":"ok","version":"0.1.0"}
```
5. **Access the API documentation**:
Open `http://localhost:8000/docs` in a browser to view the Swagger UI generated by FastAPI.
### Frontend Setup
1. **Navigate to the frontend directory**:
```bash
cd frontend
```
2. **Install dependencies**:
```bash
npm install
```
3. **Start the development server**:
```bash
npm run dev
```
The Vite dev server starts at `http://localhost:5173` with hot module replacement (HMR). API requests to `/api` and `/health` are automatically proxied to the backend at `http://localhost:8000` as configured in `vite.config.ts`.
4. **Build for production**:
```bash
npm run build
```
The production build outputs to `frontend/dist/` with code-split chunks for ECharts, React, and vendor libraries.
### Running Both Together
For the best development experience, run the backend and frontend in separate terminal sessions:
- Terminal 1: `uv run uvicorn visual_notebook.main:app --reload --port 8000`
- Terminal 2: `cd frontend && npm run dev`
Visit `http://localhost:5173` for the full application with live reloading on both frontend and backend changes.
## Project Structure Overview
```
va_project/
pyproject.toml # Root workspace config
Dockerfile # Combined build (frontend + backend)
Dockerfile.backend # Backend-only build
docker-compose.yml # Development Docker Compose
datasets/ # Uploaded CSV files
checkpoints/ # Session checkpoint files
static/ # Built frontend (production)
backend/
pyproject.toml # Backend dependencies
visual_notebook/
main.py # FastAPI application entry
config.py # Settings from environment
session.py # Session and SessionStore
api/
routes.py # API endpoint definitions
schemas.py # Pydantic request/response models
deps.py # Dependency injection
domain/
dataframe.py # DataFrameRef wrapper
history.py # History tree (Memento)
operations.py # Operation registry
checkpoints.py # Checkpoint persistence
validators.py # Validation helpers
data/
csv_loader.py # CSV loading and export
schema.py # Schema inference
types.py # Column type classification
viz/
_common.py # Shared chart utilities
histogram.py # Histogram computation
scatter.py # Scatter plot computation
bar_topn.py # Top-N bar chart
timeline.py # Timeline/line chart
heatmap.py # Heatmap computation
map.py # Map computation
tests/
test_history.py # History tests
test_operations.py # Operations tests
frontend/
package.json # Frontend dependencies
vite.config.ts # Vite configuration
tailwind.config.js # Tailwind CSS theme
tsconfig.json # TypeScript configuration
index.html # HTML entry point
src/
main.tsx # React entry point
App.tsx # Root component with ErrorBoundary
api/
client.ts # Axios instance and API functions
hooks.ts # React hooks for data fetching
types.ts # TypeScript type definitions
store/
session.ts # Session state store
notebook.ts # Notebook cell store
ui.ts # UI toggle store
history.ts # Undo/redo state store
components/
Header.tsx # Top navigation bar
NotebookPage.tsx # Main notebook layout
UploadPanel.tsx # CSV upload dialog
TableCellView.tsx # Table data display
ChartCellView.tsx # ECharts rendering
SchemaView.tsx # Column schema display
ManipulationPanel.tsx # Operation controls
VisualizationPanel.tsx # Chart configuration
TablePreview.tsx # Table row preview
MapCanvas.tsx # Map point visualization
ErrorToast.tsx # Error notification
lib/
chartTypes.ts # Chart type definitions
csv.ts # CSV utility functions
format.ts # Number/formatting helpers
styles/
index.css # Global styles and Tailwind
docs/ # Documentation (this directory)
```
## Adding a New Operation
Follow these steps to add a new data operation (e.g., `deduplicate`) to the system:
### Step 1: Define the OperationDef
Add a new entry to the `OPERATIONS` dictionary in `backend/visual_notebook/domain/operations.py`:
```python
"deduplicate": OperationDef(
key="deduplicate",
label="Remove Duplicates",
description="Remove duplicate rows based on specified columns",
param_schema={
"type": "object",
"required": ["columns"],
"properties": {
"columns": {"type": "array", "items": {"type": "string"}},
"keep": {"type": "string", "enum": ["first", "last", "none"], "default": "first"},
},
},
validate=_validate_deduplicate,
apply=_apply_deduplicate,
),
```
### Step 2: Implement the Validate Function
```python
def _validate_deduplicate(params: dict, schema: list[dict]) -> dict:
columns = params.get("columns", [])
if not columns:
raise ValueError("'columns' list is required")
params.setdefault("keep", "first")
return params
```
The validate function should check that all required parameters are present and have valid values. It receives the raw user params and the current table schema.
### Step 3: Implement the Apply Function
Provide both DuckDB and Polars implementations:
```python
def _apply_deduplicate(ref: DataFrameRef, params: dict) -> DataFrameRef:
columns = params["columns"]
keep = params.get("keep", "first")
if ref.backend == "duckdb" and ref.name and ref.con:
new_name = f"_dedup_{uuid.uuid4().hex[:6]}"
cols = ", ".join(f'"{c}"' for c in columns)
if keep == "first":
sql = f'''
SELECT DISTINCT ON ({cols}) *
FROM "{ref.name}"
ORDER BY {cols}
'''
elif keep == "last":
sql = f'''
SELECT DISTINCT ON ({cols}) *
FROM "{ref.name}"
ORDER BY {cols} DESC
'''
else:
sql = f'''
SELECT * FROM "{ref.name}"
GROUP BY {cols}
HAVING COUNT(*) = 1
'''
ref.con.execute(f'CREATE OR REPLACE TABLE "{new_name}" AS {sql}')
return DataFrameRef(backend="duckdb", name=new_name, con=ref.con, source_path=ref.source_path)
elif ref.backend == "polars" and ref.lf is not None:
lf = ref.lf.unique(subset=columns, keep=keep)
return DataFrameRef(backend="polars", lf=lf, source_path=ref.source_path)
raise ValueError("Invalid DataFrameRef")
```
### Step 4: Add Tests
Add test cases to `backend/visual_notebook/tests/test_operations.py`:
```python
def test_deduplicate_validation():
op = OPERATIONS["deduplicate"]
with pytest.raises(ValueError):
op.validate({}, [])
result = op.validate({"columns": ["a", "b"]}, [])
assert result["keep"] == "first"
def test_deduplicate_in_registry():
assert "deduplicate" in OPERATIONS
```
### Step 5: Verify
1. Run the tests: `uv run pytest backend/visual_notebook/tests/ -v`
2. Start the server and verify the operation appears in `GET /api/ops`.
3. Test the operation via the Swagger UI at `http://localhost:8000/docs`.
## Adding a New Chart Type
Follow these steps to add a new chart type (e.g., `pie`) to the system:
### Step 1: Create the Computation Module
Create a new file `backend/visual_notebook/viz/pie.py`:
```python
"""Pie chart generation."""
from __future__ import annotations
from typing import Any
import duckdb
from visual_notebook.viz._common import PALETTE, base_chart, title, tooltip_style, palette_colors
def compute_pie(
con: duckdb.DuckDBPyConnection,
table_name: str,
category_col: str,
value_col: str | None = None,
n: int = 10,
) -> dict[str, Any]:
"""Compute pie chart data and return ECharts spec."""
if value_col:
result = con.execute(f'''
SELECT "{category_col}" as cat, SUM("{value_col}") as val
FROM "{table_name}"
WHERE "{category_col}" IS NOT NULL
GROUP BY cat
ORDER BY val DESC
LIMIT {n}
''').fetchall()
else:
result = con.execute(f'''
SELECT "{category_col}" as cat, COUNT(*) as val
FROM "{table_name}"
WHERE "{category_col}" IS NOT NULL
GROUP BY cat
ORDER BY val DESC
LIMIT {n}
''').fetchall()
data = [{"name": str(r[0]), "value": float(r[1])} for r in result]
colors = palette_colors(len(data))
chart = base_chart()
chart["title"] = title(f"Distribution of {category_col}")
chart["tooltip"] = {**tooltip_style(), "trigger": "item", "formatter": "{b}: {c} ({d}%)"}
chart["legend"] = {"orient": "vertical", "left": "left", "top": "middle"}
chart["series"] = [{
"type": "pie",
"radius": ["40%", "70%"],
"data": data,
"emphasis": {"itemStyle": {"shadowBlur": 10, "shadowOffsetX": 0, "shadowColor": "rgba(0,0,0,0.5)"}},
}]
return chart
```
### Step 2: Register in the Route Handler
Add the import and handler in `backend/visual_notebook/api/routes.py`:
```python
from visual_notebook.viz.pie import compute_pie
# In the create_chart endpoint:
elif req.chart_type == "pie":
spec = compute_pie(con, table, params["category_col"], params.get("value_col"), params.get("n", 10))
```
### Step 3: Add Frontend Support
Update `frontend/src/lib/chartTypes.ts` to include the new chart type, and add a configuration form in the `VisualizationPanel` component.
## Adding a New Frontend Component
### Step 1: Create the Component File
Create a new file in `frontend/src/components/`:
```typescript
// StatsPanel.tsx
import React from 'react';
import { useSessionStore } from '../store/session';
export function StatsPanel() {
const { columns, rowCount } = useSessionStore();
return (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-sm font-semibold text-gray-700 mb-3">Dataset Statistics</h3>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-500">Total Rows</span>
<span className="font-mono">{rowCount.toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500">Total Columns</span>
<span className="font-mono">{columns.length}</span>
</div>
</div>
</div>
);
}
```
### Step 2: Use Tailwind Classes
Follow the project's Tailwind conventions:
- Use `brand-*` colors for primary actions and accents.
- Use `gray-*` colors for neutral text, borders, and backgrounds.
- Use `text-sm` for body text, `text-xs` for secondary labels.
- Use `rounded-lg` for card corners, `shadow` for elevation.
- Use `flex` and `grid` for layout, with `gap-*` for spacing.
### Step 3: Integrate into the Application
Import and render the component in the appropriate parent:
```typescript
// In NotebookPage.tsx
import { StatsPanel } from './StatsPanel';
// Render conditionally
{showStatsPanel && <StatsPanel />}
```
### Step 4: Add a UI Toggle
If the component should be togglable, add a flag to `useUIStore`:
```typescript
showStatsPanel: boolean;
toggleStatsPanel: () => void;
```
## Testing
### Backend Tests
The backend uses pytest with test files in `backend/visual_notebook/tests/`.
**Run all tests**:
```bash
uv run pytest backend/visual_notebook/tests/ -v
```
**Run a specific test file**:
```bash
uv run pytest backend/visual_notebook/tests/test_operations.py -v
```
**Run with coverage** (if pytest-cov is installed):
```bash
uv run pytest backend/visual_notebook/tests/ -v --cov=visual_notebook
```
**Test structure**:
- `test_history.py`: Tests for commit, undo, redo, branch, and tree serialization.
- `test_operations.py`: Tests for the operations registry, parameter validation, and default values.
### Frontend Testing
The frontend currently does not have automated tests configured. For future implementation, consider:
- **Vitest**: Fast unit testing framework compatible with Vite.
- **React Testing Library**: Component testing with DOM assertions.
- **Playwright**: End-to-end testing for API integration.
## Code Style and Conventions
### Python (Backend)
- **Line length**: 100 characters maximum.
- **Type hints**: Use type hints on all function signatures. Import `from __future__ import annotations` for modern type syntax.
- **Docstrings**: Use triple-quoted docstrings for all public functions and classes.
- **Naming**: `snake_case` for functions and variables, `PascalCase` for classes.
- **Imports**: Standard library first, then third-party, then local. Use absolute imports within the package.
- **f-strings**: Use f-strings for SQL query construction (the SQL injection risk is mitigated by server-generated table names and column names from the schema).
- **Error handling**: Raise `ValueError` in validators, `HTTPException` in route handlers.
### TypeScript (Frontend)
- **Strict mode**: TypeScript strict mode is enabled. All variables must have explicit or inferred types.
- **Functional components**: Use function declarations with explicit return types.
- **Naming**: `camelCase` for variables and functions, `PascalCase` for components and types.
- **Imports**: Use relative paths for local modules, package names for third-party libraries.
- **Hooks**: Custom hooks in `api/hooks.ts` follow the `use[X]` naming pattern and return `{ action, loading, error }`.
- **CSS**: Use Tailwind utility classes exclusively. Avoid inline styles except for dynamic values (chart dimensions, SVG coordinates).
## Git Workflow
### Branch Naming
- `feature/[description]`: New features
- `fix/[description]`: Bug fixes
- `docs/[description]`: Documentation changes
- `refactor/[description]`: Code refactoring
### Commit Messages
Follow conventional commit format:
```
type(scope): description
[optional body]
```
Types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
Examples:
- `feat(operations): add deduplicate operation`
- `fix(history): correct undo pointer after branch`
- `docs(api): update endpoint descriptions`
### Pull Request Process
1. Create a feature branch from `main`.
2. Implement changes with tests.
3. Run the full test suite: `uv run pytest`.
4. Ensure the frontend builds: `cd frontend && npm run build`.
5. Open a pull request with a description of the changes and any manual testing performed.
6. Request review from a maintainer.
7. Address review feedback and re-run tests.
8. Merge when approved and all checks pass.
|