Contributing

Contributing

Welcome to ClickLens! This guide will help you set up your development environment and contribute to the project.

1. Prerequisites

  • Bun (latest version) - Package manager and runtime
  • Node.js (20.x) - For production builds
  • Git - Version control
  • A ClickHouse server - For testing

2. Quick Start

# Clone the repository
git clone https://github.com/ntk148v/clicklens.git
cd clicklens
 
# Install dependencies
bun install
 
# Copy environment configuration
cp env.sample .env.local
 
# Start the development server
bun dev

The app will be available at http://localhost:3000 (opens in a new tab).

3. Project Structure

clicklens/
├── src/
│   ├── app/                    # Next.js App Router
│   ├── components/             # React components
│   └── lib/                    # Core libraries
├── docs/                       # Nextra documentation
├── e2e/                        # Playwright E2E tests
├── public/                     # Static assets
└── scripts/                    # Build/utility scripts

3.1. Key Directories

3.1.1. src/app/ - Next.js Routes

The App Router structure with route groups:

DirectoryPurpose
(app)/Authenticated routes (wrapped in layout with sidebar)
api/API route handlers
login/Public authentication page

3.1.2. src/components/ - UI Components

DirectoryPurpose
auth/AuthProvider and useAuth hook
discover/Discover feature: QueryBar, FieldsSidebar, DiscoverGrid, DiscoverHistogram
layout/Sidebar, Header, ConnectionStatus
logging/Log viewer components
monitoring/Monitoring tabs (13 components)
queries/Query analytics components
sql/SQL Console: SqlEditor, ResultGrid, QueryTabs, etc. (12 components)
tables/Table explorer tabs (7 components)
ui/shadcn/ui base components (30 components)

3.1.3. src/lib/ - Core Libraries

Directory/FilePurpose
auth/Session management (iron-session configuration)
clickhouse/ClickHouse client, config, types
hooks/Custom React hooks (6 hooks)
rbac/Feature role definitions
store/Zustand stores (tabs.ts, sql-browser.ts, access.ts)
types/TypeScript type definitions
sql/SQL parsing utilities
utils.tsGeneral utility functions
clickhouse-functions.tsClickHouse function definitions for autocomplete

4. Development Workflow

4.1. Running the Dev Server

bun dev

This starts:

  • Next.js dev server with hot reload
  • TypeScript type checking
  • Tailwind CSS JIT compilation

4.2. Linting

bun lint

We use ESLint with the Next.js configuration. All code must pass linting before merge.

4.3. Building

bun run build

This creates a production-optimized build in .next/standalone/.

4.4. Testing

4.4.1. Unit Tests (Bun Test)

# Run all tests
bun run test
 
# Run single test file
bun run test src/path/to/file.test.ts
 
# Filter by test name
bun run test --filter "pattern"
 
# With coverage report
bun run test:coverage

Test files: .test.ts/.test.tsx, co-located with source.

Example:

import { describe, it, expect, mock, beforeEach } from "bun:test";
 
// Mock modules
const mockFn = mock();
mock.module("@/lib/module", () => ({ fn: mockFn }));
 
describe("Feature", () => {
  beforeEach(() => {
    mockFn.mockReset();
    mockFn.mockResolvedValue("default");
  });
 
  it("handles success", async () => {
    mockFn.mockResolvedValue("result");
    const result = await myFunction();
    expect(result).toBe("result");
  });
 
  it("handles error", async () => {
    mockFn.mockRejectedValue(new Error("fail"));
    await expect(myFunction()).rejects.toThrow("fail");
  });
});

4.4.2. E2E Tests (Playwright)

# Run all E2E tests
bun run test:e2e
 
# Run with UI mode
bun run test:e2e:ui

E2E tests are located in the e2e/ directory.

5. Code Style Guidelines

5.1. TypeScript

  • Strict mode is enabled
  • Prefer type over interface unless extension is needed
  • No any without justification (use unknown with narrowing)
  • Validate external inputs with Zod

5.2. Imports (ES Modules Only)

Order (top to bottom, alphabetize within groups):

  1. External packages
  2. Next.js core (next/server, etc.)
  3. Internal libs (@/lib/...)
  4. UI components (@/components/...)
  5. Relative imports from same directory

Example:

import { NextRequest, NextResponse } from "next/server";
import { getSessionClickHouseConfig } from "@/lib/auth";
import { createClient } from "@/lib/clickhouse";
import { formatQueryError } from "@/lib/errors";
import { StatCard } from "@/components/shared/StatCard";
import { validateSqlStatement } from "./validator";

5.3. React Components

  • Default to Server Components where possible
  • Add "use client" only when required (state, effects, browser APIs)
  • Colocate route-specific components under their route segment
  • Shared components go in src/components/

5.4. State Management

  • Use Zustand for global client state
  • Keep stores focused and minimal
  • Persist only essential data (not results, not large objects)

5.5. Error Handling

  • Use formatQueryError from @/lib/errors for ClickHouse errors
  • Categorizes errors: SYNTAX, SCHEMA, TYPE, PERMISSION, RESOURCE, FUNCTION, SYSTEM, NETWORK, UNKNOWN
  • Sanitizes sensitive data in production (file paths, IPs, credentials, stack traces)
  • Logs full details server-side for debugging
  • Provides user-friendly messages and hints

Example:

import { formatQueryError } from "@/lib/errors";
 
try {
  const result = await client.query({ query: sql });
  return NextResponse.json({ success: true, data: result });
} catch (error) {
  const formatted = formatQueryError(error, undefined, true);
  return NextResponse.json(
    { success: false, error: formatted },
    { status: 500 },
  );
}

5.6. Styling

Refer to the UI Style Guide section below for detailed styling standards.

6. UI Style Guide

This section outlines the styling standards and best practices for the ClickLens codebase. Following these rules ensures consistency, maintainability, and a premium user experience.

6.1. Core Principles

  • Utility-First: Use Tailwind CSS for all styling. Avoid custom CSS files unless creating shared utility classes in globals.css.
  • Component-Driven: Build UIs using shared components from src/components/ui (shadcn/ui).
  • Dark Mode First: Designs should look great in dark mode (default for ClickHouse-related tools), while supporting light mode.

6.2. Typography

We use Geist Sans for UI text and Geist Mono for data/code.

  • UI Text: Use font-sans (default).
  • Data/Code: Use font-mono for all database content, SQL queries, and technical values (IDs, hashes).

6.2.1. Utility Classes

  • .data-table-cell: Applied to table cells displaying database content.
    • Definition: @apply py-1.5 px-4 font-mono text-sm;

6.3. Colors & Theming

Theme variables are defined in src/app/globals.css. Do not hardcode hex values.

CategoryUsageVariable
BackgroundPage backgroundbg-background
ForegroundPrimary texttext-foreground
MutedSecondary text/backgroundstext-muted-foreground, bg-muted
PrimaryMain actions/highlightsbg-primary, text-primary-foreground
BordersComponent bordersborder-border
InputInput bordersborder-input

6.4. Components

6.4.1. Buttons

  • Do not use raw HTML <button> tags.
  • Use import { Button } from "@/components/ui/button".
  • Variants:
    • default: Primary actions.
    • secondary: Secondary actions.
    • ghost: Icon buttons or subtle actions.
    • outline: Bordered actions.
    • destructive: Delete/Remove actions.

6.4.2. Tables

  • Use import { Table, ... } from "@/components/ui/table".
  • Use TableCell with .data-table-cell for data rows to ensure consistent padding and font.
  • For interactive rows, use ClickableTableRow.

6.4.3. Icons

  • Use lucide-react for all icons.
  • Standard size: w-4 h-4 (16px) for buttons, w-3.5 h-3.5 (14px) for subtle indicators.

6.5. Spacing & Layout

  • Padding/Margin: Use standard Tailwind spacing header (e.g., p-4, m-2).
  • Flexbox: Prefer flex and gap-* over margins for component layouts.
  • Containers: Use container mx-auto for page content wrappers.

6.6. Best Practices

  • Colocation: Keep styles close to usage (Tailwind classes).
  • Avoid Inline Styles: Do not use style={{ ... }} unless dynamic values are required (e.g., resizable widths).
  • Accessibility: ensure interactive elements have aria-label if they contain only icons.

6.7. Example: Data Grid

import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
 
export function DataGrid({ data }) {
  return (
    <Table>
      <TableBody>
        {data.map((row) => (
          <TableRow key={row.id}>
            {/* Use global utility for consistent data cells */}
            <TableCell className="data-table-cell">{row.value}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

7. Adding a New Feature

7.1. Plan the Routes

Decide where your feature lives in src/app/(app)/:

src/app/(app)/my-feature/
├── page.tsx           # Main page component
└── layout.tsx         # Optional layout wrapper

7.2. Create Components

Add feature-specific components:

src/components/my-feature/
├── MyComponent.tsx
├── AnotherComponent.tsx
└── index.ts           # Barrel export

7.3. Add API Routes (if needed)

src/app/api/clickhouse/my-feature/
└── route.ts           # Next.js route handler

7.4. Add to Navigation

Update src/components/layout/Sidebar.tsx to add your feature to the navigation.

7.5. Add Permissions (if needed)

If your feature requires specific permissions:

  1. Add the permission to src/components/auth/AuthProvider.tsx
  2. Add permission check in your page component
  3. Add the permission derivation in /api/auth/permissions/route.ts

8. API Development

8.1. Creating an API Route

Standard pattern:

// src/app/api/clickhouse/my-endpoint/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getSessionClickHouseConfig } from "@/lib/auth";
import { createClient } from "@/lib/clickhouse";
import { formatQueryError } from "@/lib/errors";
 
export async function POST(request: NextRequest) {
  try {
    // 1. CSRF/authentication
    const csrfError = await requireCsrf(request);
    if (csrfError) return csrfError;
 
    // 2. Authorization
    const authError = await checkPermission("canExecuteQueries");
    if (authError) return authError;
 
    // 3. Validation
    const body = await request.json();
    if (!body.sql) {
      return NextResponse.json(
        { success: false, error: formatHttpError(400) },
        { status: 400 },
      );
    }
 
    // 4. Main logic
    const config = await getSessionClickHouseConfig();
    const client = createClient(config);
    const result = await client.query({ query: body.sql });
 
    return NextResponse.json({ success: true, data: result });
  } catch (error) {
    // 5. Centralized error handling
    const formatted = formatQueryError(error, undefined, true);
    return NextResponse.json(
      { success: false, error: formatted },
      { status: 500 },
    );
  }
}

Error response format:

{
  success: false;
  error: {
    code: number;           // HTTP status or error code
    type: string;           // "SYNTAX", "SCHEMA", "PERMISSION", etc.
    message: string;        // Technical (sanitized in prod)
    userMessage: string;    // User-friendly
    hint?: string;          // Optional help
  };
}

Streaming: Use NDJSON for large datasets (>1000 rows) with backpressure handling. Headers: Content-Type: application/x-ndjson; charset=utf-8.

8.2. Using the Two Client Types

Lens Client (for metadata, no user context needed):

import { createLensClient } from "@/lib/clickhouse";
 
const client = createLensClient();
const databases = await client.query({ query: "SHOW DATABASES" });

User Client (for user queries):

import { getSessionClickHouseConfig } from "@/lib/auth";
import { createClient } from "@/lib/clickhouse";
 
const config = await getSessionClickHouseConfig();
const client = createClient(config);
const result = await client.query({ query: userSql });

9. Adding UI Components

9.1. Using shadcn/ui

We use shadcn/ui patterns. To add a new base component:

bunx shadcn-ui@latest add button

Components are added to src/components/ui/.

9.2. Creating Custom Components

Follow the existing patterns in src/components/ui/:

// src/components/ui/my-component.tsx
import { cn } from "@/lib/utils";
 
interface MyComponentProps {
  className?: string;
  children: React.ReactNode;
}
 
export function MyComponent({ className, children }: MyComponentProps) {
  return <div className={cn("base-classes", className)}>{children}</div>;
}

10. Security

  • Never log sensitive data (passwords, tokens, PII) in production
  • Sanitize user inputs before ClickHouse (validateSqlStatement)
  • Enable CSRF protection for state-changing routes
  • Rate limit expensive operations (auth, queries)
  • Security headers pre-configured in next.config.ts (HSTS, CSP, X-Frame-Options, etc.)

11. Performance

  • Stream large result sets (>1000 rows) with NDJSON + backpressure
  • Debounce/throttle user input handlers (search, autocomplete)
  • Use useMemo/useCallback sparingly (only after profiling)
  • Cache expensive operations (ClickHouse metadata, RBAC checks)

12. Contributing Checklist

Before submitting a PR:

  • Code passes bun lint
  • Code passes bun run build
  • New features have appropriate permissions checks
  • API routes handle errors gracefully
  • Components are properly typed
  • No console.log statements (use proper error handling)
  • PR description explains the change

13. Getting Help

  • Issues: Open a GitHub issue for bugs or feature requests
  • Discussions: Use GitHub Discussions for questions
  • Documentation: Check docs/internal/PROJECT_AUDIT.md for detailed architecture info

For detailed architecture information, refer to the internal audit document at docs/internal/PROJECT_AUDIT.md.