Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Agent Guidelines β€” widget-layout

## Project Overview

Drag-and-drop dashboard widget layout for Red Hat Hybrid Cloud Console (HCC). Users customize their landing page by adding, removing, repositioning, and resizing widgets loaded via Webpack Module Federation from other HCC applications.

## Tech Stack

| Layer | Technology |
|-------|-----------|
| UI framework | React 18, TypeScript 5 |
| Component library | PatternFly 6 (`@patternfly/react-core`, `@patternfly/react-component-groups`) |
| Dashboard engine | `@patternfly/widgetized-dashboard` (prerelease) + `react-grid-layout` |
| State management | Jotai (atoms in `src/state/`) |
| Build system | Webpack via `fec` CLI (`@redhat-cloud-services/frontend-components-config`) |
| Module federation | Scalprum / `@scalprum/react-core` for loading remote widgets |
| Routing | React Router DOM v6 |
| Styling | SCSS (co-located with components) + PatternFly utility classes |
| Unit testing | Jest 27 + React Testing Library |
| E2E testing | Playwright |
| Linting | ESLint + Stylelint (SCSS) |

## Directory Structure

```
src/
β”œβ”€β”€ api/ # API client functions (dashboard-templates.ts)
β”œβ”€β”€ Components/ # PascalCase component directories
β”‚ β”œβ”€β”€ DnDLayout/ # Core grid layout (GridLayout, ConvertWidgetMapping)
β”‚ β”œβ”€β”€ WidgetDrawer/ # Widget selection drawer
β”‚ β”œβ”€β”€ Header/ # Dashboard header with actions
β”‚ β”œβ”€β”€ DashboardHub/ # Multi-dashboard management views
β”‚ β”œβ”€β”€ Widgets/ # Built-in widget components
β”‚ β”œβ”€β”€ CreateModal/ # Dashboard creation dialog
β”‚ β”œβ”€β”€ DuplicateModal/ # Dashboard duplication dialog
β”‚ └── Icons/ # Custom icon components
β”œβ”€β”€ Modules/ # Federated module entry points
β”‚ β”œβ”€β”€ DashboardHub.tsx # Multi-dashboard hub (exposed as ./DashboardHub)
β”‚ └── GenericDashboardPage.tsx
β”œβ”€β”€ Routes/ # Route components
β”‚ └── Default/ # Default dashboard route (exposed as ./WidgetLayout)
β”œβ”€β”€ hooks/ # Custom React hooks (camelCase)
β”‚ β”œβ”€β”€ useDashboardConfig.ts # Template loading + persistence for landing page
β”‚ β”œβ”€β”€ useDashboardTemplate.ts # Template loading for generic dashboards
β”‚ β”œβ”€β”€ useGetDashboards.ts # Fetch all user dashboards
β”‚ └── tests/ # Hook unit tests
β”œβ”€β”€ state/ # Jotai atoms (camelCase)
β”‚ β”œβ”€β”€ templateAtom.ts # Current template config
β”‚ β”œβ”€β”€ layoutAtom.ts # Active layout variant + active item
β”‚ β”œβ”€β”€ widgetMappingAtom.ts # Widget registry with permission filtering
β”‚ └── ...
β”œβ”€β”€ types/ # TypeScript type declarations
β”œβ”€β”€ consts.ts # Grid constants (columns, dropping_elem_id)
β”œβ”€β”€ AppEntry.tsx # Federation entry point (exposed as ./RootApp)
└── App.tsx # Root component
```

## Federated Module Exports

Defined in `fec.config.js`:

| Export | File | Purpose |
|--------|------|---------|
| `./RootApp` | `src/AppEntry.tsx` | Standalone app entry |
| `./WidgetLayout` | `src/Routes/Default/Default.tsx` | Embeddable dashboard layout |
| `./DashboardHub` | `src/Modules/DashboardHub.tsx` | Multi-dashboard management |

## Key Conventions

### State Management
- All shared state lives in Jotai atoms under `src/state/`
- Use `useAtomValue` for read-only, `useSetAtom` for write-only, `useAtom` for read-write
- Never use React context or Redux for new state β€” Jotai only
- Derived/async atoms (e.g., `resolvedWidgetMappingAtom`) handle side effects like permission filtering

### Component Patterns
- Component directories use **PascalCase** (`DnDLayout/`, `WidgetDrawer/`)
- Hooks and atoms use **camelCase** (`useDashboardConfig.ts`, `templateAtom.ts`)
- Co-locate SCSS with component files
- Use PatternFly components (`PageSection`, `EmptyState`, `Button`, etc.) β€” don't build custom equivalents
- Access Chrome services via `useChrome()` hook (auth, analytics, visibility functions)

### API Layer
- All API functions live in `src/api/dashboard-templates.ts`
- Two API surfaces:
- `/api/chrome-service/v1/dashboard-templates/` β€” base templates, widget mapping, legacy template CRUD
- `/api/widget-layout/v1/` β€” user dashboard CRUD, import/export, copy, set default
- Template persistence is debounced (1500ms via `awesome-debounce-promise`)
- Widget IDs follow `"widgetType#uuid"` format (separator: `#`)
- Error handling uses custom `DashboardTemplatesError` class with HTTP status

### Responsive Grid
- Four breakpoints: `sm` (800px), `md` (1100px), `lg` (1400px), `xl` (1550px)
- Column counts: `sm: 1`, `md: 2`, `lg: 3`, `xl: 4`
- Sidebar mode uses narrower breakpoints (500/800/1100/1250px)
- Templates store layout per breakpoint (`TemplateConfig = { [Variants]: LayoutWithTitle[] }`)

### Widget System
- Widgets are remote federated modules loaded via Scalprum
- Widget mapping fetched from backend, filtered by user permissions
- `ConvertWidgetMapping.tsx` transforms Scalprum widget config β†’ PatternFly widget config (icon strings β†’ React elements)
- Each widget has `defaults` (w, h, maxH, minH) and optional `config` (icon, title, headerLink, permissions)

## Common Pitfalls

1. **Don't use global `DB` patterns** β€” this is a frontend app, state is in Jotai atoms
2. **Don't import PatternFly v5 classes** β€” this repo uses PatternFly v6 (`pf-v6-*` prefix)
3. **Widget IDs contain `#` separator** β€” always use `widgetIdSeparator` constant and `mapWidgetDefaults()` to parse
4. **Template has UI-only fields** β€” `ExtendedLayoutItem.widgetType`, `.config`, `.locked` are NOT persisted to backend; only `LayoutWithTitle` fields go to API
5. **Mock `useChrome` in tests** β€” it provides auth, analytics, and visibility functions from Chrome shell
6. **Mock `@scalprum/react-core`** β€” `ScalprumComponent` loads remote modules at runtime; tests must mock it
7. **`crypto.randomUUID` needs polyfill** in jsdom test environment
8. **Two template hooks exist** β€” `useDashboardConfig` (landing page, uses global atoms) vs `useDashboardTemplate` (generic dashboards, local state)

## Documentation Index

| Document | Description |
|----------|-------------|
| [README.md](README.md) | Setup, getting started, local development |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Contribution workflow and conventions |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | System design, data flow, deployment |
| [docs/testing-guidelines.md](docs/testing-guidelines.md) | Jest and Playwright testing patterns |
| [docs/component-development-guidelines.md](docs/component-development-guidelines.md) | Widget and component development patterns |
| [docs/layout-data-format.md](docs/layout-data-format.md) | Layout data structures and API endpoints |
| [docs/ai-agent-guidelines.md](docs/ai-agent-guidelines.md) | Guidelines for AI-generated code documentation |
| [docs/components/](docs/components/) | Individual component documentation |
37 changes: 37 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@AGENTS.md

## Commands

### Build
```bash
npm run build # Production build via fec
npm run verify # Build + lint + test (full verification)
```

### Test
```bash
npm test # Jest unit tests (passWithNoTests)
npm run test:playwright # Playwright e2e tests
npm run test:ct # Cypress component tests
```

### Lint
```bash
npm run lint # ESLint + Stylelint
npm run lint:js # ESLint only
npm run lint:sass # Stylelint (SCSS) only
npm run lint:js:fix # ESLint with auto-fix
```

### Dev Server
```bash
npm run start # Start dev proxy (HOT reload enabled)
CONFIG_PORT=8000 npm run start # With local chrome-service-backend
```

## Notes

- Run `npm run verify` before suggesting a PR β€” it runs build, lint, and test in sequence
- Default branch is `master`
- Jest config collects coverage from `src/**/*.js` (not `.ts`/`.tsx` β€” coverage may show 0% for TypeScript files, but tests still run)
- Snapshot tests exist in `src/Components/DnDLayout/__snapshots__/` β€” update with `npm test -- -u` if intentional changes
74 changes: 74 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Contributing to widget-layout

## Prerequisites

- Node.js >= 16.20.2
- npm >= 7.24.2
- `/etc/hosts` entry for `*.foo.redhat.com` (run `npm run patch:hosts`)

## Setup

```bash
git clone <repo-url>
cd widget-layout
npm install
npm run start
```

Open the URL shown in terminal output. For local backend integration, see [README.md](README.md#run-locally-with-chrome-service-be).

## Development Workflow

1. Create a feature branch from `master`
2. Make changes following the conventions in [AGENTS.md](AGENTS.md)
3. Run `npm run verify` (build + lint + test)
4. Commit using conventional commit format
5. Open a PR against `master`

## Commit Conventions

Use [Conventional Commits](https://www.conventionalcommits.org/):

```
type(scope): short description
```

**Types**: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `style`

**Scopes**: `grid`, `widgets`, `hub`, `api`, `state`, `header`, `drawer`

**Examples**:
```
feat(grid): add snap-to-grid for widget placement
fix(api): handle 404 when template not found
refactor(state): consolidate layout atoms
test(hooks): add useDashboardConfig error handling tests
docs: update layout data format documentation
```

**Rules**:
- Title line ≀ 50 characters
- Imperative mood ("add" not "added")
- Reference Jira ticket key in commit body (not title)
- Atomic commits β€” one logical change per commit

## Testing

Run tests before submitting:

```bash
npm test # Unit tests (Jest)
npm run test:playwright # E2E tests (Playwright)
npm run lint # Linting (ESLint + Stylelint)
npm run verify # All of the above + build
```

See [Testing Guidelines](docs/testing-guidelines.md) for patterns and conventions.

## PR Guidelines

- Keep PRs focused on a single concern
- Include screenshots for UI changes
- Ensure CI passes (lint, test, build)
- Update documentation if behavior changes
- Link related Jira tickets
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ Reusable drag and drop UI layout for frontends

## Documentation

- [Widget Layout Component Interface](docs/components/widget-layout.md) - Complete interface documentation for the WidgetLayout federated module
- [GridLayout Component](docs/components/grid-layout.md) - Core layout engine with drag-and-drop, responsive grid system, and template persistence
- [Creating Custom Widgets](docs/components/custom-widgets.md) - Guide for creating custom widgets that integrate with the grid layout system
- [Layout Data Format](docs/layout-data-format.md) - Comprehensive documentation of the layout data structures, API endpoints, and data flow
- [AI Agent Guidelines](docs/ai-agent-guidelines.md) - Guidelines for AI agents generating code documentation
| Document | Description |
|----------|-------------|
| [AGENTS.md](AGENTS.md) | AI agent onboarding: tech stack, conventions, pitfalls |
| [CONTRIBUTING.md](CONTRIBUTING.md) | Contribution workflow, commit conventions, PR guidelines |
| [Architecture](docs/ARCHITECTURE.md) | System design, data flow, widget pipeline, deployment |
| [Testing Guidelines](docs/testing-guidelines.md) | Jest and Playwright testing patterns |
| [Component Development](docs/component-development-guidelines.md) | Widget and component development patterns |
| [Layout Data Format](docs/layout-data-format.md) | Layout data structures, API endpoints, and data flow |
| [Widget Layout Component](docs/components/widget-layout.md) | WidgetLayout federated module interface |
| [GridLayout Component](docs/components/grid-layout.md) | Core layout engine with drag-and-drop |
| [Creating Custom Widgets](docs/components/custom-widgets.md) | Custom widget integration guide |
| [AI Agent Guidelines](docs/ai-agent-guidelines.md) | Guidelines for AI-generated code documentation |

## Initial etc/hosts setup

Expand Down
100 changes: 100 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Architecture

## System Overview

widget-layout is a React micro-frontend that runs inside the Red Hat Hybrid Cloud Console (HCC) shell. It provides a customizable dashboard where users arrange widgets loaded from other HCC applications via Webpack Module Federation.

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ HCC Shell (insights-chrome) β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ widget-layout (this repo) β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Widget A β”‚ β”‚ Widget B β”‚ β”‚Widget Cβ”‚ β”‚ β”‚
β”‚ β”‚ β”‚(remote) β”‚ β”‚(remote) β”‚ β”‚(remote)β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ Backend: chrome-service-backend (Go) β”‚
β”‚ Backend: widget-layout-backend (Go) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Key Architectural Decisions

### Federated Modules

The app exposes three federated modules via `fec.config.js`:

- **`./RootApp`** β€” Full standalone app (AppEntry β†’ App β†’ DefaultRoute)
- **`./WidgetLayout`** β€” Embeddable grid layout component for the landing page
- **`./DashboardHub`** β€” Multi-dashboard management interface with routing

Widgets rendered inside the grid are themselves remote federated modules loaded via **Scalprum** (`@scalprum/react-core`). The widget mapping (which modules to load) comes from the backend.

### State Management (Jotai)

All shared state uses Jotai atoms, avoiding Redux or Context:

| Atom | Purpose |
|------|---------|
| `templateAtom` | Current dashboard template config (all breakpoints) |
| `templateIdAtom` | Active template database ID |
| `layoutVariantAtom` | Current responsive breakpoint (`sm`/`md`/`lg`/`xl`) |
| `widgetMappingAtom` | Available widgets (filtered by user permissions) |
| `resolvedWidgetMappingAtom` | Write-only atom that fetches + filters widget mapping |
| `drawerExpandedAtom` | Widget drawer open/closed state |
| `currentDropInItemAtom` | Widget type being dragged into grid |
| `currentlyUsedWidgetsAtom` | List of widget types currently on dashboard |
| `lockedLayoutAtom` | Whether layout editing is disabled |
| `notificationsAtom` | Toast notification queue |

### Two Dashboard Modes

1. **Landing Page** (`useDashboardConfig` hook) β€” Uses global Jotai atoms (`templateAtom`, `templateIdAtom`). Template fetched via `/api/chrome-service/v1/dashboard-templates`. Persists via PATCH to chrome-service.

2. **Dashboard Hub** (`useDashboardTemplate` hook) β€” Uses local React state. Template fetched via `/api/widget-layout/v1/{id}`. Supports multiple named dashboards with create, copy, import/export, delete, and set-default operations.

### Backend API Integration

Two backend services provide APIs:

| Service | Base Path | Responsibilities |
|---------|-----------|-----------------|
| chrome-service-backend | `/api/chrome-service/v1/dashboard-templates/` | Base templates, widget mapping, legacy template CRUD |
| widget-layout-backend | `/api/widget-layout/v1/` | User dashboards, import/export, copy, set default |

Template changes are persisted with a **1500ms debounce** to avoid excessive API calls during drag operations.

### Responsive Grid System

Layouts are stored per-breakpoint. When the viewport resizes, the active breakpoint changes and the corresponding layout is rendered:

| Breakpoint | Min Width | Columns | Use Case |
|------------|-----------|---------|----------|
| `sm` | 0px | 1 | Mobile |
| `md` | 1100px | 2 | Tablet |
| `lg` | 1400px | 3 | Desktop |
| `xl` | 1550px | 4 | Wide desktop |

When a widget is added, it's inserted into all four breakpoint layouts with appropriate sizing constraints (width capped to available columns per breakpoint).

### Widget Mapping Pipeline

```
Backend (JSON) ConvertWidgetMapping.tsx PatternFly GridLayout
───────────────── ──→ ──────────────────────── ──→ ──────────────────
{ - icon string β†’ React Renders widgets with
scope, module, element (PatternFly icons) correct icons, titles,
importName, - adds renderWidget fn links, and permissions
defaults, config (ScalprumComponent)
} - adds wrapperProps,
cardBodyProps
```

## Deployment

- **Frontend CRD**: `deploy/frontend.yaml` defines the Kubernetes `Frontend` custom resource
- **Build**: `fec build` produces static assets
- **CI**: `pr_check.sh` and `build_deploy.sh` scripts for Konflux/Jenkins pipelines
- **Proxy**: Dev server uses `fec dev-proxy` with optional local chrome-service-backend routing
Loading
Loading