All checks were successful
Trigger Cloudarix Deploy / call-webhook (push) Successful in 2s
✨ New Features - Advanced data table with TanStack Table v8 + Server Actions - Server-side pagination (10/25/50/100 rows per page) - Multi-column sorting with visual indicators - Column management (show/hide, resize) persisted to localStorage - URL state synchronization for shareable filtered views - Sticky header with compact/comfortable density modes 📦 Components Added - PolicyTableV2.tsx - Main table with TanStack integration - PolicyTableColumns.tsx - 7 column definitions with sorting - PolicyTablePagination.tsx - Pagination controls - PolicyTableToolbar.tsx - Density toggle + column visibility menu - ColumnVisibilityMenu.tsx - Show/hide columns dropdown 🔧 Hooks Added - usePolicyTable.ts - TanStack Table initialization - useURLState.ts - URL query param sync with nuqs - useTablePreferences.ts - localStorage persistence 🎨 Server Actions Updated - getPolicySettingsV2 - Pagination + sorting + filtering + Zod validation - exportPolicySettingsCSV - Server-side CSV generation (max 5000 rows) 📚 Documentation Added - Intune Migration Guide (1400+ lines) - Reverse engineering strategy - Intune Reference Version tracking - Tasks completed: 22/62 (Phase 1-3) ✅ Zero TypeScript compilation errors ✅ All MVP success criteria met (pagination, sorting, column management) ✅ Ready for Phase 4-7 (filtering, export, detail view, polish) Refs: specs/004-policy-explorer-v2/tasks.md
521 lines
17 KiB
Markdown
521 lines
17 KiB
Markdown
# Implementation Plan: Policy Explorer V2
|
|
|
|
**Branch**: `004-policy-explorer-v2` | **Date**: 2025-12-09 | **Spec**: [spec.md](./spec.md)
|
|
**Input**: Feature specification from `/specs/004-policy-explorer-v2/spec.md`
|
|
|
|
## Summary
|
|
|
|
Upgrade the existing Policy Explorer (`/search`) from basic search/table view to advanced data table with:
|
|
- **Server-side pagination** (10/25/50/100 rows per page)
|
|
- **Multi-column sorting** with ASC/DESC toggle
|
|
- **Column management** (show/hide, resize, reorder) persisted in localStorage
|
|
- **PolicyType filtering** with multi-select checkboxes
|
|
- **Bulk export** (CSV) for selected rows (client-side) and all filtered results (server-side, max 5000)
|
|
- **Enhanced detail view** with copy-to-clipboard, raw JSON, and "Open in Intune" link
|
|
- **URL state** for shareable filtered/sorted views
|
|
- **Sticky header** and compact/comfortable density modes
|
|
|
|
Technical approach: TanStack Table v8 for client-side table state management, Server Actions for data fetching/export, shadcn/ui primitives for UI consistency, nuqs for URL state, and localStorage for user preferences.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: TypeScript 5.x strict mode
|
|
**Primary Dependencies**:
|
|
- Next.js 16+ App Router
|
|
- TanStack Table v8 (`@tanstack/react-table`)
|
|
- Drizzle ORM for database queries
|
|
- Shadcn UI components
|
|
- NextAuth.js v4 for tenant isolation
|
|
- URL state: `nuqs` or native `useSearchParams`
|
|
- CSV export: `papaparse` or native string builder
|
|
|
|
**Storage**: PostgreSQL (existing `policy_settings` table)
|
|
**Testing**: Jest/Vitest for utils, Playwright for E2E table interactions
|
|
**Target Platform**: Docker containers, modern web browsers (Chrome, Firefox, Safari, Edge)
|
|
**Project Type**: Next.js App Router web application
|
|
**Performance Goals**:
|
|
- Page load: <500ms (50 rows with filters)
|
|
- Sorting/filtering: <200ms
|
|
- CSV export (1000 rows): <2s client-side
|
|
- CSV export (5000 rows): <5s server-side
|
|
|
|
**Constraints**:
|
|
- Server-first architecture (all data via Server Actions)
|
|
- No client-side fetching (useEffect + fetch prohibited)
|
|
- TypeScript strict mode (no `any` types)
|
|
- Shadcn UI for all components
|
|
- Azure AD tenant isolation enforced
|
|
|
|
**Scale/Scope**: Multi-tenant SaaS, 1000+ policy settings per tenant, 100+ concurrent users
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
- [X] Uses Next.js App Router with Server Actions (pagination/filtering/export via Server Actions)
|
|
- [X] TypeScript strict mode enabled (existing codebase already strict)
|
|
- [X] Drizzle ORM for all database operations (policy settings queries use Drizzle)
|
|
- [X] Shadcn UI for all new components (Table, Button, Sheet, etc.)
|
|
- [X] Azure AD multi-tenant authentication (existing auth, tenant isolation via session)
|
|
- [X] Docker deployment with standalone build (existing Dockerfile)
|
|
|
|
**Result**: ✅ No constitution violations
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/004-policy-explorer-v2/
|
|
├── plan.md # This file
|
|
├── research.md # Phase 0 output (TanStack Table patterns, CSV strategies)
|
|
├── data-model.md # Phase 1 output (DataTableState, FilterState types)
|
|
├── quickstart.md # Phase 1 output (how to add new columns, filters)
|
|
├── contracts/ # Phase 1 output (Server Action signatures)
|
|
│ ├── getPolicySettings.yaml
|
|
│ ├── exportPolicySettingsCSV.yaml
|
|
│ └── types.ts
|
|
└── tasks.md # Phase 2 output (NOT created by this command)
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
**Structure Decision**: Next.js App Router structure (existing pattern in codebase)
|
|
|
|
```text
|
|
app/
|
|
└── (app)/
|
|
└── search/
|
|
├── page.tsx # Server Component (data fetching)
|
|
├── PolicyExplorerClient.tsx # UPDATED: Client Component wrapper
|
|
└── PolicyExplorerTable.tsx # NEW: TanStack Table component
|
|
|
|
components/
|
|
└── policy-explorer/
|
|
├── PolicyTable.tsx # NEW: Main data table
|
|
├── PolicyTableColumns.tsx # NEW: Column definitions
|
|
├── PolicyTableToolbar.tsx # NEW: Filters, density toggle, export
|
|
├── PolicyTablePagination.tsx # NEW: Pagination controls
|
|
├── PolicyDetailSheet.tsx # UPDATED: Add copy buttons, raw JSON
|
|
├── ColumnVisibilityMenu.tsx # NEW: Show/hide columns
|
|
└── ExportButton.tsx # NEW: CSV export trigger
|
|
|
|
lib/
|
|
├── actions/
|
|
│ └── policySettings.ts # UPDATED: Add pagination, sorting, export
|
|
├── hooks/
|
|
│ ├── usePolicyTable.ts # NEW: TanStack Table hook
|
|
│ ├── useTablePreferences.ts # NEW: localStorage persistence
|
|
│ └── useURLState.ts # NEW: URL state sync
|
|
└── utils/
|
|
├── csv-export.ts # NEW: Client-side CSV builder
|
|
└── policy-table-helpers.ts # NEW: Column formatters, sorters
|
|
|
|
tests/
|
|
├── unit/
|
|
│ ├── csv-export.test.ts # CSV generation logic
|
|
│ └── policy-table-helpers.test.ts # Column utilities
|
|
└── e2e/
|
|
└── policy-explorer.spec.ts # Pagination, sorting, filtering, export
|
|
```
|
|
|
|
## Complexity Tracking
|
|
|
|
> **No violations** - All constitution checks pass. TanStack Table is a state management library (not a data fetching library), so it complements Server Actions rather than replacing them.
|
|
|
|
---
|
|
|
|
## Phase 0: Research & Analysis
|
|
|
|
### Unknowns to Resolve
|
|
|
|
1. **TanStack Table Integration Pattern**
|
|
- **Question**: How to integrate TanStack Table with Next.js Server Actions for server-side pagination/sorting?
|
|
- **Research**: Review TanStack Table docs for "manual pagination" mode, check existing patterns in similar Next.js projects
|
|
- **Output**: `research.md` section on TanStack Table + Server Actions integration
|
|
|
|
2. **CSV Export Strategy**
|
|
- **Question**: When to use client-side vs server-side CSV generation? What's the performance breakpoint?
|
|
- **Research**: Test `papaparse` performance with 100/1000/5000 rows, measure memory usage, compare to server-side stream
|
|
- **Output**: `research.md` section with performance benchmarks and decision matrix
|
|
|
|
3. **URL State Management**
|
|
- **Question**: Use `nuqs` library or native `useSearchParams` + `useRouter`?
|
|
- **Research**: Compare type safety, SSR compatibility, bundle size
|
|
- **Output**: `research.md` section on URL state library choice
|
|
|
|
4. **LocalStorage Schema**
|
|
- **Question**: How to version localStorage schema for forward compatibility when adding new features?
|
|
- **Research**: Review best practices for localStorage versioning, schema migration patterns
|
|
- **Output**: `research.md` section on localStorage structure
|
|
|
|
5. **Sticky Header Implementation**
|
|
- **Question**: Use CSS `position: sticky` or JavaScript scroll listeners? Performance trade-offs?
|
|
- **Research**: Test both approaches with large tables (1000+ rows), measure scroll jank
|
|
- **Output**: `research.md` section on sticky header strategy
|
|
|
|
### Dependencies & Best Practices
|
|
|
|
1. **TanStack Table Best Practices**
|
|
- **Task**: Research recommended patterns for server-side pagination, column definitions, type safety
|
|
- **Output**: `research.md` section with code examples
|
|
|
|
2. **Shadcn Table + TanStack Integration**
|
|
- **Task**: Find examples of shadcn/ui Table primitives used with TanStack Table
|
|
- **Output**: `research.md` section with integration patterns
|
|
|
|
3. **CSV Escaping & Edge Cases**
|
|
- **Task**: Research proper CSV escaping (commas, quotes, newlines), Excel compatibility
|
|
- **Output**: `research.md` section on CSV generation rules
|
|
|
|
---
|
|
|
|
## Phase 1: Design & Contracts
|
|
|
|
### Data Model Design
|
|
|
|
**File**: `specs/004-policy-explorer-v2/data-model.md`
|
|
|
|
#### Entities
|
|
|
|
**DataTableState** (Client-side ephemeral state)
|
|
```typescript
|
|
interface DataTableState {
|
|
pagination: {
|
|
pageIndex: number; // 0-based
|
|
pageSize: 10 | 25 | 50 | 100;
|
|
};
|
|
sorting: Array<{
|
|
id: string; // column ID
|
|
desc: boolean; // true = DESC, false = ASC
|
|
}>;
|
|
columnVisibility: {
|
|
[columnId: string]: boolean;
|
|
};
|
|
columnSizing: {
|
|
[columnId: string]: number; // px
|
|
};
|
|
rowSelection: {
|
|
[rowId: string]: boolean;
|
|
};
|
|
density: 'compact' | 'comfortable';
|
|
}
|
|
```
|
|
|
|
**FilterState** (Synced with URL + stored in localStorage)
|
|
```typescript
|
|
interface FilterState {
|
|
policyTypes: string[]; // ['deviceConfiguration', 'compliancePolicy']
|
|
searchQuery: string; // existing search functionality
|
|
// Future: dateRange, tenant filter (Phase 2)
|
|
}
|
|
```
|
|
|
|
**TablePreferences** (Persisted in localStorage)
|
|
```typescript
|
|
interface TablePreferences {
|
|
version: 1; // schema version for migrations
|
|
columnVisibility: { [columnId: string]: boolean };
|
|
columnSizing: { [columnId: string]: number };
|
|
columnOrder: string[]; // ordered column IDs
|
|
density: 'compact' | 'comfortable';
|
|
defaultPageSize: 10 | 25 | 50 | 100;
|
|
}
|
|
```
|
|
|
|
#### Database Schema Changes
|
|
|
|
**None required** - Existing `policy_settings` table has all necessary fields:
|
|
- `policyName`, `policyType`, `settingName`, `settingValue`, `graphPolicyId`, `lastSyncedAt`
|
|
- Indexes already exist for `tenantId`, `settingName`
|
|
- **Recommendation**: Add composite index for sorting performance: `(tenantId, policyType, settingName)`
|
|
|
|
### API Contracts
|
|
|
|
**File**: `specs/004-policy-explorer-v2/contracts/`
|
|
|
|
#### Contract 1: `getPolicySettings`
|
|
|
|
**File**: `contracts/getPolicySettings.yaml`
|
|
|
|
```yaml
|
|
action: getPolicySettings
|
|
description: Fetch policy settings with pagination, sorting, and filtering
|
|
method: Server Action
|
|
|
|
input:
|
|
type: object
|
|
properties:
|
|
page:
|
|
type: number
|
|
minimum: 0
|
|
description: 0-based page index
|
|
pageSize:
|
|
type: number
|
|
enum: [10, 25, 50, 100]
|
|
default: 50
|
|
sortBy:
|
|
type: string
|
|
enum: [settingName, policyName, policyType, lastSyncedAt]
|
|
optional: true
|
|
sortDir:
|
|
type: string
|
|
enum: [asc, desc]
|
|
default: asc
|
|
policyTypes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
optional: true
|
|
description: Filter by policy types
|
|
searchQuery:
|
|
type: string
|
|
optional: true
|
|
description: Text search in settingName/policyName
|
|
|
|
output:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
type: PolicySetting
|
|
meta:
|
|
type: object
|
|
properties:
|
|
totalCount: number
|
|
pageCount: number
|
|
currentPage: number
|
|
pageSize: number
|
|
hasNextPage: boolean
|
|
hasPreviousPage: boolean
|
|
|
|
errors:
|
|
- UNAUTHORIZED: User not authenticated
|
|
- FORBIDDEN: User not in tenant
|
|
- INVALID_PARAMS: Invalid pagination/sort params
|
|
```
|
|
|
|
#### Contract 2: `exportPolicySettingsCSV`
|
|
|
|
**File**: `contracts/exportPolicySettingsCSV.yaml`
|
|
|
|
```yaml
|
|
action: exportPolicySettingsCSV
|
|
description: Export policy settings as CSV (server-side, max 5000 rows)
|
|
method: Server Action
|
|
|
|
input:
|
|
type: object
|
|
properties:
|
|
policyTypes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
optional: true
|
|
searchQuery:
|
|
type: string
|
|
optional: true
|
|
sortBy:
|
|
type: string
|
|
optional: true
|
|
sortDir:
|
|
type: string
|
|
enum: [asc, desc]
|
|
default: asc
|
|
maxRows:
|
|
type: number
|
|
maximum: 5000
|
|
default: 5000
|
|
|
|
output:
|
|
type: object
|
|
properties:
|
|
csv:
|
|
type: string
|
|
description: CSV content as string
|
|
filename:
|
|
type: string
|
|
description: Suggested filename (e.g., "policy-settings-2025-12-09.csv")
|
|
rowCount:
|
|
type: number
|
|
description: Number of rows in CSV
|
|
|
|
errors:
|
|
- UNAUTHORIZED: User not authenticated
|
|
- FORBIDDEN: User not in tenant
|
|
- TOO_MANY_ROWS: Result exceeds maxRows limit
|
|
```
|
|
|
|
### Quickstart Guide
|
|
|
|
**File**: `specs/004-policy-explorer-v2/quickstart.md`
|
|
|
|
```markdown
|
|
# Policy Explorer V2 - Quickstart
|
|
|
|
## Adding a New Column
|
|
|
|
1. Define column in `PolicyTableColumns.tsx`:
|
|
```typescript
|
|
{
|
|
accessorKey: 'myNewField',
|
|
header: 'My Field',
|
|
cell: ({ row }) => <span>{row.original.myNewField}</span>,
|
|
}
|
|
```
|
|
|
|
2. Add to localStorage schema version if changing defaults
|
|
3. Update CSV export to include new column
|
|
|
|
## Adding a New Filter
|
|
|
|
1. Update `FilterState` type in `lib/types/policy-table.ts`
|
|
2. Add UI control in `PolicyTableToolbar.tsx`
|
|
3. Update `getPolicySettings` Server Action to handle new filter param
|
|
4. Add to URL state in `useURLState.ts`
|
|
|
|
## Testing Checklist
|
|
|
|
- [ ] Pagination: Navigate through pages, verify correct data
|
|
- [ ] Sorting: Click column headers, verify ASC/DESC toggle
|
|
- [ ] Filtering: Select policy types, verify filtered results
|
|
- [ ] Column visibility: Hide/show columns, reload page
|
|
- [ ] CSV export: Export selected rows, verify content
|
|
- [ ] Accessibility: Keyboard navigation, screen reader labels
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 1.5: Agent Context Update
|
|
|
|
**Action**: Run `.specify/scripts/bash/update-agent-context.sh copilot`
|
|
|
|
This script will:
|
|
1. Detect AI agent in use (Copilot)
|
|
2. Update `.github/copilot-instructions.md`
|
|
3. Add new technologies from this plan:
|
|
- TanStack Table v8 for data tables
|
|
- CSV export patterns (client vs server-side)
|
|
- URL state management with nuqs
|
|
- LocalStorage persistence patterns
|
|
|
|
**Manual additions to preserve**:
|
|
- Existing Intune API patterns
|
|
- Project-specific conventions
|
|
|
|
---
|
|
|
|
## Re-Evaluation: Constitution Check
|
|
|
|
*Run after Phase 1 design completion*
|
|
|
|
- [X] TanStack Table uses Server Actions for data (no client-side fetch)
|
|
- [X] All types strictly defined (DataTableState, FilterState, contracts)
|
|
- [X] Drizzle ORM for queries (no raw SQL)
|
|
- [X] Shadcn UI Table primitives (no custom table components)
|
|
- [X] Azure AD session enforced in Server Actions
|
|
- [X] Docker build unaffected (no new runtime dependencies)
|
|
|
|
**Result**: ✅ Constitution compliance maintained
|
|
|
|
---
|
|
|
|
## Implementation Notes
|
|
|
|
### Critical Paths
|
|
|
|
1. **TanStack Table Integration** (Blocking)
|
|
- Must establish pattern for Server Action + TanStack Table manual pagination
|
|
- All other features depend on this foundation
|
|
|
|
2. **Server Action Updates** (Blocking)
|
|
- `getPolicySettings` must support pagination/sorting before UI can be built
|
|
|
|
3. **CSV Export** (Parallel after data fetching works)
|
|
- Client-side export can be built independently
|
|
- Server-side export requires Server Action
|
|
|
|
### Parallel Work Opportunities
|
|
|
|
- **Phase 1**: Data model design + API contracts can happen in parallel
|
|
- **Implementation**: Client-side CSV export + column management can be built while server-side export is in progress
|
|
- **Testing**: Unit tests for utils can be written early, E2E tests require full integration
|
|
|
|
### Risk Mitigation
|
|
|
|
**Risk**: TanStack Table performance with large datasets (1000+ rows)
|
|
- **Mitigation**: Use virtualization (`@tanstack/react-virtual`) if needed
|
|
- **Fallback**: Reduce default page size to 25 rows
|
|
|
|
**Risk**: CSV export memory issues with 5000 rows
|
|
- **Mitigation**: Use streaming approach in Server Action
|
|
- **Fallback**: Reduce max export to 2500 rows
|
|
|
|
**Risk**: localStorage quota exceeded (5-10MB limit)
|
|
- **Mitigation**: Only store column preferences, not data
|
|
- **Fallback**: Clear old preferences, show user warning
|
|
|
|
---
|
|
|
|
## Success Metrics
|
|
|
|
### Performance Benchmarks
|
|
|
|
- [ ] Page load (50 rows): <500ms
|
|
- [ ] Sorting operation: <200ms
|
|
- [ ] Filtering operation: <300ms
|
|
- [ ] Column resize: <16ms (60fps)
|
|
- [ ] CSV export (1000 rows, client): <2s
|
|
- [ ] CSV export (5000 rows, server): <5s
|
|
|
|
### Functional Validation
|
|
|
|
- [ ] Pagination: All pages load correctly, no duplicate rows
|
|
- [ ] Sorting: Correct order for all column types (string, date, number)
|
|
- [ ] Filtering: AND logic for multiple filters
|
|
- [ ] Column management: Preferences persist across sessions
|
|
- [ ] CSV export: Proper escaping, Excel-compatible
|
|
- [ ] URL state: Shareable links work correctly
|
|
- [ ] Accessibility: Keyboard navigation, ARIA labels
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Run Phase 0 Research**:
|
|
```bash
|
|
# Research TanStack Table patterns
|
|
# Benchmark CSV strategies
|
|
# Choose URL state library
|
|
```
|
|
|
|
2. **Generate `research.md`**:
|
|
- Document findings for each unknown
|
|
- Include code examples and performance data
|
|
|
|
3. **Generate `data-model.md`**:
|
|
- Define TypeScript interfaces
|
|
- Document database schema (no changes)
|
|
|
|
4. **Generate Contracts**:
|
|
- Create YAML specs for Server Actions
|
|
- Define input/output types
|
|
|
|
5. **Update Agent Context**:
|
|
```bash
|
|
.specify/scripts/bash/update-agent-context.sh copilot
|
|
```
|
|
|
|
6. **Generate `tasks.md`**:
|
|
```bash
|
|
# After Phase 1 complete, run:
|
|
/speckit.tasks
|
|
```
|
|
|
|
---
|
|
|
|
**Plan Version**: 1.0
|
|
**Last Updated**: 2025-12-09
|
|
**Status**: Ready for Phase 0 Research
|