re-uploading work
This commit is contained in:
123
docs/web-ui/pack-configuration-display.md
Normal file
123
docs/web-ui/pack-configuration-display.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Pack Configuration Display
|
||||
|
||||
## Overview
|
||||
|
||||
The Pack detail page now displays pack configuration in a unified view that combines the configuration schema (`conf_schema`) with actual configuration values (`config`).
|
||||
|
||||
## Features
|
||||
|
||||
### Configuration Section
|
||||
|
||||
When viewing a pack that has configuration properties defined in its schema, a "Configuration" section is automatically displayed on the pack detail page.
|
||||
|
||||
### Display Components
|
||||
|
||||
The configuration display shows:
|
||||
|
||||
1. **Property Name**: The configuration key (displayed in monospace font)
|
||||
2. **Type Badge**: The data type (string, boolean, integer, number, array, object)
|
||||
3. **Default Badge**: A yellow badge indicating when the default value is being used
|
||||
4. **Description**: Human-readable description from the schema
|
||||
5. **Current Value**: The actual configuration value, formatted based on type
|
||||
6. **Range Information**: For numeric types with min/max constraints
|
||||
|
||||
### Value Formatting
|
||||
|
||||
Values are formatted intelligently based on their type:
|
||||
|
||||
- **Boolean**: Green checkmark badge for `true`, gray badge for `false`
|
||||
- **Numbers**: Displayed in monospace font
|
||||
- **Strings**: Plain text (truncated if over 50 characters)
|
||||
- **Arrays**: Shows item count (e.g., "[3 items]")
|
||||
- **Objects**: Shows key count (e.g., "{5 keys}")
|
||||
- **Not Set**: Displays as italic gray "not set" text
|
||||
|
||||
### Default Value Handling
|
||||
|
||||
When a configuration property has a default value defined in the schema but no actual value is set in `config`:
|
||||
- The default value is displayed
|
||||
- A yellow "default" badge indicates it's using the schema default
|
||||
- No "default" badge appears when an explicit value is set
|
||||
|
||||
## Example
|
||||
|
||||
For a pack with the following schema and config:
|
||||
|
||||
```yaml
|
||||
conf_schema:
|
||||
type: object
|
||||
properties:
|
||||
max_action_timeout:
|
||||
type: integer
|
||||
description: "Maximum timeout for action execution in seconds"
|
||||
default: 300
|
||||
minimum: 1
|
||||
maximum: 3600
|
||||
enable_debug_logging:
|
||||
type: boolean
|
||||
description: "Enable debug logging for core pack actions"
|
||||
default: false
|
||||
required: []
|
||||
|
||||
config:
|
||||
max_action_timeout: 300
|
||||
enable_debug_logging: false
|
||||
```
|
||||
|
||||
The UI will display:
|
||||
|
||||
```
|
||||
Configuration
|
||||
─────────────────────────────────────────────────
|
||||
|
||||
max_action_timeout [integer]
|
||||
Maximum timeout for action execution in seconds
|
||||
300
|
||||
Range: 1 - 3600
|
||||
|
||||
enable_debug_logging [boolean]
|
||||
Enable debug logging for core pack actions
|
||||
✗ false
|
||||
```
|
||||
|
||||
## No Configuration
|
||||
|
||||
If a pack has no `conf_schema` properties defined, the Configuration section is not displayed.
|
||||
|
||||
## Implementation
|
||||
|
||||
- **Component**: `PackConfiguration` in `web/src/pages/packs/PacksPage.tsx`
|
||||
- **Value Renderer**: `ConfigValue` helper component for type-specific formatting
|
||||
- **Location**: Displayed in the pack detail view, after "Pack Information" card
|
||||
|
||||
## API Data
|
||||
|
||||
The configuration display uses data from the pack detail endpoint:
|
||||
|
||||
```
|
||||
GET /api/v1/packs/{ref}
|
||||
```
|
||||
|
||||
Response includes:
|
||||
- `conf_schema`: JSON Schema defining configuration structure
|
||||
- `config`: JSON object with actual configuration values
|
||||
|
||||
Both fields are already included in the `PackResponse` DTO.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Navigate to any pack detail page: `/packs/{ref}`
|
||||
2. If the pack has configuration properties, scroll to the "Configuration" section
|
||||
3. View current values, types, and descriptions
|
||||
4. See which values are using defaults (yellow badge)
|
||||
5. For numeric values, view valid range constraints
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Inline editing of configuration values
|
||||
- Validation against schema constraints
|
||||
- Configuration history/audit trail
|
||||
- Environment-specific configuration overrides
|
||||
- Secret/sensitive value masking
|
||||
- Configuration export/import
|
||||
440
docs/web-ui/web-ui-pack-testing.md
Normal file
440
docs/web-ui/web-ui-pack-testing.md
Normal file
@@ -0,0 +1,440 @@
|
||||
# Web UI Pack Testing Integration
|
||||
|
||||
## Overview
|
||||
|
||||
The Attune web UI now includes comprehensive pack testing capabilities, allowing users to view test results, execute tests, and monitor pack quality through a visual interface.
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Pack Detail Page - Test Results
|
||||
|
||||
The pack detail page displays the latest test results and provides quick access to test history.
|
||||
|
||||
**Location**: `/packs/{ref}`
|
||||
|
||||
**Features**:
|
||||
- Latest test status badge (passed/failed/skipped)
|
||||
- Test summary (total, passed, failed, skipped)
|
||||
- Pass rate percentage
|
||||
- Test execution timestamp
|
||||
- "Run Tests" button for manual test execution
|
||||
- Toggle between latest results and test history
|
||||
|
||||
### 2. Test Result Display Component
|
||||
|
||||
**Component**: `PackTestResult`
|
||||
|
||||
Displays detailed test execution results with:
|
||||
- Overall status indicator (passed/failed)
|
||||
- Test statistics (total, passed, failed, skipped)
|
||||
- Pass rate and duration
|
||||
- Expandable test suites
|
||||
- Individual test case results with error messages
|
||||
- stdout/stderr output for failed tests
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
import PackTestResult from '@/components/packs/PackTestResult';
|
||||
|
||||
<PackTestResult
|
||||
result={testResultData}
|
||||
showDetails={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### 3. Test History Component
|
||||
|
||||
**Component**: `PackTestHistory`
|
||||
|
||||
Displays a paginated list of all test executions for a pack.
|
||||
|
||||
**Features**:
|
||||
- Chronological list of test executions
|
||||
- Status badges for each execution
|
||||
- Trigger reason indicators (register, manual, ci, schedule)
|
||||
- Expandable details for each execution
|
||||
- Load more pagination
|
||||
- Date/time formatting
|
||||
- Duration display
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
import PackTestHistory from '@/components/packs/PackTestHistory';
|
||||
|
||||
<PackTestHistory
|
||||
executions={testExecutions}
|
||||
isLoading={false}
|
||||
onLoadMore={() => loadNextPage()}
|
||||
hasMore={hasMorePages}
|
||||
/>
|
||||
```
|
||||
|
||||
### 4. Test Status Badge
|
||||
|
||||
**Component**: `PackTestBadge`
|
||||
|
||||
Compact status indicator showing test results.
|
||||
|
||||
**Variants**:
|
||||
- `passed` - Green with checkmark
|
||||
- `failed` - Red with X
|
||||
- `skipped` - Gray with clock
|
||||
- Size options: `sm`, `md`, `lg`
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
import PackTestBadge from '@/components/packs/PackTestBadge';
|
||||
|
||||
<PackTestBadge
|
||||
status="passed"
|
||||
passed={76}
|
||||
total={76}
|
||||
size="md"
|
||||
showCounts={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### 5. Pack Registration Page
|
||||
|
||||
**Location**: `/packs/register`
|
||||
|
||||
Form for registering packs from local filesystem with test control options.
|
||||
|
||||
**Form Fields**:
|
||||
- **Pack Directory Path** (required) - Absolute path to pack directory
|
||||
- **Skip Tests** (checkbox) - Skip test execution during registration
|
||||
- **Force Registration** (checkbox) - Proceed even if tests fail or pack exists
|
||||
|
||||
**Features**:
|
||||
- Real-time validation
|
||||
- Loading state during registration
|
||||
- Success/error messages
|
||||
- Automatic redirect to pack details on success
|
||||
- Info panel explaining registration process
|
||||
- Help section with guidance
|
||||
|
||||
## API Integration
|
||||
|
||||
### React Query Hooks
|
||||
|
||||
#### `usePackLatestTest(packRef: string)`
|
||||
|
||||
Fetches the latest test result for a pack.
|
||||
|
||||
```tsx
|
||||
const { data: latestTest } = usePackLatestTest('core');
|
||||
|
||||
// Returns: { data: PackTestExecution | null }
|
||||
```
|
||||
|
||||
#### `usePackTestHistory(packRef: string, params)`
|
||||
|
||||
Fetches paginated test history for a pack.
|
||||
|
||||
```tsx
|
||||
const { data: testHistory } = usePackTestHistory('core', {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
});
|
||||
|
||||
// Returns: { data: { items: PackTestExecution[], meta: PaginationMeta } }
|
||||
```
|
||||
|
||||
#### `useExecutePackTests()`
|
||||
|
||||
Mutation hook for executing pack tests.
|
||||
|
||||
```tsx
|
||||
const executeTests = useExecutePackTests();
|
||||
|
||||
await executeTests.mutateAsync('core');
|
||||
// Executes tests and invalidates test queries
|
||||
```
|
||||
|
||||
#### `useRegisterPack()`
|
||||
|
||||
Mutation hook for registering packs.
|
||||
|
||||
```tsx
|
||||
const registerPack = useRegisterPack();
|
||||
|
||||
await registerPack.mutateAsync({
|
||||
path: '/path/to/pack',
|
||||
force: false,
|
||||
skipTests: false
|
||||
});
|
||||
```
|
||||
|
||||
## User Workflows
|
||||
|
||||
### View Test Results
|
||||
|
||||
1. Navigate to `/packs`
|
||||
2. Click on a pack name
|
||||
3. View latest test results in the main section
|
||||
4. Click "View History" to see all test executions
|
||||
5. Click on any execution to expand details
|
||||
|
||||
### Run Tests Manually
|
||||
|
||||
1. Navigate to pack detail page
|
||||
2. Click "Run Tests" button in sidebar (or main section)
|
||||
3. Wait for test execution (button shows "Running Tests...")
|
||||
4. Results update automatically on completion
|
||||
5. View detailed results in the expanded test result component
|
||||
|
||||
### Register a New Pack
|
||||
|
||||
1. Navigate to `/packs`
|
||||
2. Click "+ Register Pack" button
|
||||
3. Fill in the pack directory path (e.g., `/home/user/packs/mypack`)
|
||||
4. Optionally check "Skip Tests" to bypass test execution
|
||||
5. Optionally check "Force Registration" to override conflicts
|
||||
6. Click "Register Pack"
|
||||
7. Wait for registration to complete
|
||||
8. View results and test outcomes
|
||||
9. Automatically redirected to pack details page
|
||||
|
||||
### Register Pack with Tests Disabled
|
||||
|
||||
Use this workflow during development:
|
||||
|
||||
1. Navigate to `/packs/register`
|
||||
2. Enter pack path
|
||||
3. **Check "Skip Tests"**
|
||||
4. Click "Register Pack"
|
||||
5. Pack is registered without validation
|
||||
6. Later, manually run tests from pack detail page
|
||||
|
||||
### Force Re-registration
|
||||
|
||||
Use this to replace an existing pack:
|
||||
|
||||
1. Navigate to `/packs/register`
|
||||
2. Enter pack path (same as existing pack)
|
||||
3. **Check "Force Registration"**
|
||||
4. Optionally check "Skip Tests" for faster iteration
|
||||
5. Click "Register Pack"
|
||||
6. Existing pack is deleted and replaced
|
||||
7. Tests run (unless skipped)
|
||||
|
||||
## Visual Design
|
||||
|
||||
### Color Scheme
|
||||
|
||||
- **Passed Tests**: Green (`green-600`, `green-50`)
|
||||
- **Failed Tests**: Red (`red-600`, `red-50`)
|
||||
- **Skipped Tests**: Gray (`gray-600`, `gray-50`)
|
||||
- **Trigger Types**:
|
||||
- Register: Blue
|
||||
- Manual: Purple
|
||||
- CI: Green
|
||||
- Schedule: Yellow
|
||||
|
||||
### Icons
|
||||
|
||||
- **CheckCircle** - Passed tests
|
||||
- **XCircle** - Failed tests
|
||||
- **Clock** - Skipped tests
|
||||
- **AlertCircle** - Unknown/error state
|
||||
- **Play** - Run tests button
|
||||
- **Calendar** - Test execution date
|
||||
- **Clock** - Test duration
|
||||
- **ChevronDown/ChevronRight** - Expandable sections
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Test Execution Failures
|
||||
|
||||
When tests fail:
|
||||
- Error message displayed at top of page
|
||||
- Red alert banner with error details
|
||||
- Test results still stored (if execution completed)
|
||||
- User can retry by clicking "Run Tests" again
|
||||
|
||||
### Registration Failures
|
||||
|
||||
When registration fails:
|
||||
- Red alert box with error message
|
||||
- Form remains filled for corrections
|
||||
- Common errors:
|
||||
- "Pack directory does not exist"
|
||||
- "pack.yaml not found"
|
||||
- "Pack already exists" (suggest using force)
|
||||
- "Tests failed" (suggest using force or fixing tests)
|
||||
|
||||
### Network Errors
|
||||
|
||||
When API calls fail:
|
||||
- Error toast/alert
|
||||
- Retry button available
|
||||
- State preserved for manual retry
|
||||
|
||||
## Accessibility
|
||||
|
||||
- All interactive elements keyboard accessible
|
||||
- ARIA labels on icon buttons
|
||||
- Color is not the only indicator (icons + text)
|
||||
- Focus states clearly visible
|
||||
- Proper heading hierarchy
|
||||
- Screen reader friendly status messages
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Data Fetching
|
||||
|
||||
- `staleTime: 30000` (30 seconds) on test queries
|
||||
- Automatic refetch on window focus
|
||||
- Pagination for test history (10 items per page)
|
||||
- Query invalidation after test execution
|
||||
|
||||
### Optimizations
|
||||
|
||||
- Expandable sections to reduce initial render
|
||||
- Lazy loading of test details
|
||||
- Memoized components for large lists
|
||||
- Efficient re-renders with React Query
|
||||
|
||||
## Responsive Design
|
||||
|
||||
- Mobile-friendly layouts
|
||||
- Stacked columns on small screens
|
||||
- Touch-friendly tap targets (min 44x44px)
|
||||
- Horizontal scroll for wide tables
|
||||
- Collapsible sections for mobile
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Features
|
||||
|
||||
1. **Real-time Test Execution**
|
||||
- WebSocket updates during test runs
|
||||
- Progress bar showing test completion
|
||||
- Live stdout/stderr streaming
|
||||
|
||||
2. **Test Comparison**
|
||||
- Compare test results across versions
|
||||
- Show performance regressions
|
||||
- Highlight newly failing tests
|
||||
|
||||
3. **Test Filtering**
|
||||
- Filter by status (passed/failed/skipped)
|
||||
- Filter by trigger type
|
||||
- Date range filtering
|
||||
- Search by test name
|
||||
|
||||
4. **Test Analytics**
|
||||
- Trend charts (pass rate over time)
|
||||
- Flaky test detection
|
||||
- Duration trends
|
||||
- Test coverage metrics
|
||||
|
||||
5. **Bulk Actions**
|
||||
- Run tests for multiple packs
|
||||
- Batch pack registration
|
||||
- Export test results (CSV, JSON)
|
||||
|
||||
6. **Notifications**
|
||||
- Browser notifications for test completion
|
||||
- Email alerts for test failures
|
||||
- Slack/webhook integrations
|
||||
|
||||
## Development Notes
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
web/src/
|
||||
├── components/
|
||||
│ └── packs/
|
||||
│ ├── PackTestResult.tsx # Detailed test result display
|
||||
│ ├── PackTestBadge.tsx # Status badge component
|
||||
│ └── PackTestHistory.tsx # Test history list
|
||||
├── hooks/
|
||||
│ └── usePackTests.ts # React Query hooks
|
||||
└── pages/
|
||||
└── packs/
|
||||
├── PackDetailPage.tsx # Shows latest test results
|
||||
└── PackRegisterPage.tsx # Pack registration form
|
||||
```
|
||||
|
||||
### Adding New Components
|
||||
|
||||
1. Create component in `components/packs/`
|
||||
2. Export from component file
|
||||
3. Add to relevant pages
|
||||
4. Update types if needed
|
||||
5. Test build: `npm run build`
|
||||
|
||||
### API Client Updates
|
||||
|
||||
When backend API changes:
|
||||
|
||||
1. Start API server: `cargo run --bin attune-api`
|
||||
2. Regenerate client: `cd web && npm run generate:api`
|
||||
3. Update hook imports if service names changed
|
||||
4. Update TypeScript types in hooks
|
||||
5. Test and update components
|
||||
|
||||
### Testing
|
||||
|
||||
Manual testing checklist:
|
||||
|
||||
- [ ] Pack list page loads
|
||||
- [ ] Pack detail page shows test results
|
||||
- [ ] Test history displays correctly
|
||||
- [ ] Run tests button works
|
||||
- [ ] Pack registration form submits
|
||||
- [ ] Success/error messages display
|
||||
- [ ] Redirects work correctly
|
||||
- [ ] Test details expand/collapse
|
||||
- [ ] Status badges show correct colors
|
||||
- [ ] Mobile layout works
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot read property 'result' of undefined"
|
||||
|
||||
Test data not loaded yet. Add loading check:
|
||||
```tsx
|
||||
{latestTest?.data && <PackTestResult result={latestTest.data.result} />}
|
||||
```
|
||||
|
||||
### Test history not updating
|
||||
|
||||
Manually invalidate queries after test execution:
|
||||
```tsx
|
||||
queryClient.invalidateQueries({ queryKey: ['pack-tests', packRef] });
|
||||
```
|
||||
|
||||
### Styling not applied
|
||||
|
||||
- Check Tailwind classes are valid
|
||||
- Verify `lucide-react` is installed
|
||||
- Run `npm install` to ensure dependencies
|
||||
- Clear build cache: `rm -rf dist && npm run build`
|
||||
|
||||
### API calls failing (401/403)
|
||||
|
||||
- Check access token in localStorage
|
||||
- Verify token not expired
|
||||
- Log in again to refresh token
|
||||
- Check CORS configuration
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Pack Testing Framework](./pack-testing-framework.md) - Overall testing design
|
||||
- [Pack Testing User Guide](./PACK_TESTING.md) - CLI and API usage
|
||||
- [Pack Testing API](./api-pack-testing.md) - API endpoint reference
|
||||
- [Pack Install Integration](./pack-install-testing.md) - Installation with testing
|
||||
- [Web UI Architecture](./web-ui-architecture.md) - Frontend architecture
|
||||
|
||||
## Changelog
|
||||
|
||||
- **2026-01-22**: Initial web UI integration
|
||||
- Pack test result display component
|
||||
- Test history component
|
||||
- Status badge component
|
||||
- Pack registration page
|
||||
- React Query hooks for test data
|
||||
- Integration with pack detail page
|
||||
318
docs/web-ui/websocket-usage.md
Normal file
318
docs/web-ui/websocket-usage.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# WebSocket Usage in Web UI
|
||||
|
||||
## Overview
|
||||
|
||||
The Attune web UI uses a **single shared WebSocket connection** for real-time notifications across all pages. This connection is managed by `WebSocketProvider` and accessed via React hooks.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
App.tsx
|
||||
└── WebSocketProvider (manages single WS connection)
|
||||
├── EventsPage (subscribes to "event" notifications)
|
||||
├── ExecutionsPage (subscribes to "execution" notifications)
|
||||
└── DashboardPage (subscribes to multiple entity types)
|
||||
```
|
||||
|
||||
Only **one WebSocket connection** is created per browser tab, regardless of how many pages subscribe to notifications.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### 1. Ensure Provider is Configured
|
||||
|
||||
The `WebSocketProvider` should be set up in `App.tsx` (already configured):
|
||||
|
||||
```typescript
|
||||
import { WebSocketProvider } from "@/contexts/WebSocketContext";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<WebSocketProvider>
|
||||
{/* Your app routes */}
|
||||
</WebSocketProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Subscribe to Entity Notifications
|
||||
|
||||
Use `useEntityNotifications` to receive real-time updates for a specific entity type:
|
||||
|
||||
```typescript
|
||||
import { useCallback } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useEntityNotifications } from "@/contexts/WebSocketContext";
|
||||
|
||||
function MyPage() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// IMPORTANT: Wrap handler in useCallback for stable reference
|
||||
const handleNotification = useCallback(() => {
|
||||
// Invalidate queries to refetch data
|
||||
queryClient.invalidateQueries({ queryKey: ["myEntity"] });
|
||||
}, [queryClient]);
|
||||
|
||||
const { connected } = useEntityNotifications("myEntity", handleNotification);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connected && <span>Live updates enabled</span>}
|
||||
{/* Rest of your component */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Available Entity Types
|
||||
|
||||
Subscribe to these entity types to receive notifications:
|
||||
|
||||
- `"event"` - Event creation/updates
|
||||
- `"execution"` - Execution status changes
|
||||
- `"enforcement"` - Rule enforcement events
|
||||
- `"inquiry"` - Human-in-the-loop interactions
|
||||
- `"action"` - Action changes (from pack updates)
|
||||
- `"rule"` - Rule changes
|
||||
- `"trigger"` - Trigger changes
|
||||
- `"sensor"` - Sensor changes
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Subscriptions in One Component
|
||||
|
||||
```typescript
|
||||
function DashboardPage() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleEventNotification = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["events"] });
|
||||
}, [queryClient]);
|
||||
|
||||
const handleExecutionNotification = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["executions"] });
|
||||
}, [queryClient]);
|
||||
|
||||
useEntityNotifications("event", handleEventNotification);
|
||||
useEntityNotifications("execution", handleExecutionNotification);
|
||||
|
||||
// Both subscriptions share the same WebSocket connection
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Notification Handling
|
||||
|
||||
```typescript
|
||||
import { useCallback } from "react";
|
||||
import { useEntityNotifications, Notification } from "@/contexts/WebSocketContext";
|
||||
|
||||
function MyPage() {
|
||||
const handleNotification = useCallback((notification: Notification) => {
|
||||
console.log("Received notification:", notification);
|
||||
|
||||
// Access notification details
|
||||
console.log("Entity type:", notification.entity_type);
|
||||
console.log("Entity ID:", notification.entity_id);
|
||||
console.log("Payload:", notification.payload);
|
||||
console.log("Timestamp:", notification.timestamp);
|
||||
|
||||
// Custom logic based on notification
|
||||
if (notification.entity_id === specificId) {
|
||||
// Do something specific
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEntityNotifications("execution", handleNotification);
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Subscriptions
|
||||
|
||||
```typescript
|
||||
function MyPage({ entityType }: { entityType: string }) {
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
|
||||
const handleNotification = useCallback(() => {
|
||||
// Handle notification
|
||||
}, []);
|
||||
|
||||
// Only subscribe when enabled is true
|
||||
useEntityNotifications(entityType, handleNotification, enabled);
|
||||
}
|
||||
```
|
||||
|
||||
### Direct Context Access
|
||||
|
||||
For advanced use cases, access the WebSocket context directly:
|
||||
|
||||
```typescript
|
||||
import { useWebSocketContext } from "@/contexts/WebSocketContext";
|
||||
|
||||
function AdvancedComponent() {
|
||||
const { connected, subscribe, unsubscribe } = useWebSocketContext();
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (notification) => {
|
||||
// Custom handling
|
||||
};
|
||||
|
||||
// Subscribe to a custom filter
|
||||
subscribe("entity_type:execution", handler);
|
||||
|
||||
return () => {
|
||||
unsubscribe("entity_type:execution", handler);
|
||||
};
|
||||
}, [subscribe, unsubscribe]);
|
||||
}
|
||||
```
|
||||
|
||||
## Important Guidelines
|
||||
|
||||
### ✅ DO
|
||||
|
||||
- **Always wrap handlers in `useCallback`** to prevent re-subscriptions
|
||||
- Include all dependencies in the `useCallback` dependency array
|
||||
- Use `queryClient.invalidateQueries` for simple cache invalidation
|
||||
- Show connection status in the UI when relevant
|
||||
|
||||
### ❌ DON'T
|
||||
|
||||
- Don't create inline functions as handlers (causes re-subscriptions)
|
||||
- Don't create multiple WebSocket connections (use the context)
|
||||
- Don't forget to handle the `connected` state
|
||||
- Don't use the deprecated `useWebSocket` from `@/hooks/useWebSocket.ts`
|
||||
|
||||
## Connection Status
|
||||
|
||||
Display connection status to users:
|
||||
|
||||
```typescript
|
||||
function MyPage() {
|
||||
const { connected } = useEntityNotifications("event", handleNotification);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{connected ? (
|
||||
<div className="flex items-center gap-2 text-green-600">
|
||||
<div className="w-2 h-2 bg-green-600 rounded-full animate-pulse" />
|
||||
<span>Live updates</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-gray-400">Connecting...</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
WebSocket connection is configured via environment variables:
|
||||
|
||||
```bash
|
||||
# .env.local or .env.development
|
||||
VITE_WS_URL=ws://localhost:8081
|
||||
```
|
||||
|
||||
The provider automatically appends `/ws` to the URL if not present.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Multiple Connections Opening
|
||||
|
||||
**Problem**: Browser DevTools shows multiple WebSocket connections.
|
||||
|
||||
**Solution**:
|
||||
- Ensure `WebSocketProvider` is only added once in `App.tsx`
|
||||
- Check that handlers are wrapped in `useCallback`
|
||||
- Verify React StrictMode isn't causing double-mounting in development
|
||||
|
||||
### Notifications Not Received
|
||||
|
||||
**Problem**: Component doesn't receive notifications.
|
||||
|
||||
**Checklist**:
|
||||
1. Is `WebSocketProvider` wrapping your app?
|
||||
2. Is the notifier service running? (`make run-notifier`)
|
||||
3. Is `connected` returning `true`?
|
||||
4. Is the entity type spelled correctly?
|
||||
5. Are notifications actually being sent? (check server logs)
|
||||
|
||||
### Connection Keeps Reconnecting
|
||||
|
||||
**Problem**: WebSocket disconnects and reconnects repeatedly.
|
||||
|
||||
**Possible causes**:
|
||||
- Notifier service restarting
|
||||
- Network issues
|
||||
- Authentication token expired (WebSocket doesn't currently use auth)
|
||||
- Check console for error messages
|
||||
|
||||
## Example: Complete Page
|
||||
|
||||
```typescript
|
||||
import { useState, useCallback } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useEntityNotifications } from "@/contexts/WebSocketContext";
|
||||
import { useEvents } from "@/hooks/useEvents";
|
||||
|
||||
export default function EventsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
// Real-time updates
|
||||
const handleEventNotification = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["events"] });
|
||||
}, [queryClient]);
|
||||
|
||||
const { connected } = useEntityNotifications("event", handleEventNotification);
|
||||
|
||||
// Fetch data
|
||||
const { data, isLoading } = useEvents({ page, pageSize: 20 });
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h1>Events</h1>
|
||||
{connected && (
|
||||
<div className="text-green-600">
|
||||
<div className="w-2 h-2 bg-green-600 rounded-full animate-pulse" />
|
||||
Live updates
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Render events */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Notes
|
||||
|
||||
- The single shared connection significantly reduces network overhead
|
||||
- Subscriptions are reference-counted (only subscribe to each filter once)
|
||||
- Handlers are called synchronously when notifications arrive
|
||||
- No polling is needed - all updates are push-based via WebSocket
|
||||
|
||||
## Migration from Old Pattern
|
||||
|
||||
If you find code using the old pattern:
|
||||
|
||||
```typescript
|
||||
// ❌ OLD - creates separate connection
|
||||
import { useEntityNotifications } from "@/hooks/useWebSocket";
|
||||
const { connected } = useEntityNotifications("event", () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["events"] });
|
||||
});
|
||||
```
|
||||
|
||||
Update to:
|
||||
|
||||
```typescript
|
||||
// ✅ NEW - uses shared connection
|
||||
import { useEntityNotifications } from "@/contexts/WebSocketContext";
|
||||
const handleNotification = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["events"] });
|
||||
}, [queryClient]);
|
||||
const { connected } = useEntityNotifications("event", handleNotification);
|
||||
```
|
||||
Reference in New Issue
Block a user