|
| 1 | +# Search Page: Filters, Pagination & UI Integration |
| 2 | + |
| 3 | +**Date**: 2025-11-16 |
| 4 | +**Branch**: `feature/search-filters-pagination` |
| 5 | +**Feature**: Complete search experience with filters, sorting, pagination, and UI integration |
| 6 | + |
| 7 | +## Overview |
| 8 | +This document tracks the implementation of comprehensive search functionality for the Commitly platform, including backend filter support and frontend UI integration. |
| 9 | + |
| 10 | +## Requirements |
| 11 | + |
| 12 | +### Backend |
| 13 | +- Extend `GET /api/v1/roadmap/catalog` endpoint with filters: |
| 14 | + - `language`: Filter by programming language |
| 15 | + - `tag`: Filter by topics/tags |
| 16 | + - `difficulty`: Filter by difficulty level |
| 17 | + - `min_rating`: Minimum average rating |
| 18 | + - `min_views`: Minimum view count |
| 19 | + - `min_syncs`: Minimum sync count |
| 20 | +- Add sorting options (already implemented: newest, most_viewed, most_synced, highest_rated, trending) |
| 21 | +- Implement proper pagination with filters |
| 22 | + |
| 23 | +### Frontend |
| 24 | +- Add filter controls for: |
| 25 | + - Language (dropdown/multi-select) |
| 26 | + - Tags/topics |
| 27 | + - Difficulty |
| 28 | + - Min rating, min views, min syncs (sliders/inputs) |
| 29 | +- Add pagination controls (page numbers/next/prev) |
| 30 | +- Wire query params → API calls → state |
| 31 | +- Separate sections for "Your Repositories" vs "Public Repositories" |
| 32 | +- Clean, non-cluttered UX |
| 33 | + |
| 34 | +## Implementation Plan |
| 35 | + |
| 36 | +### Phase 1: Backend Filter Implementation |
| 37 | +1. Update `RoadmapResultStore.list_catalog()` to accept filter parameters |
| 38 | +2. Modify SQL query to apply filters dynamically |
| 39 | +3. Update `RoadmapService.list_catalog()` to pass through filters |
| 40 | +4. Update API endpoint to accept filter query parameters |
| 41 | +5. Create Pydantic models for filter validation |
| 42 | + |
| 43 | +### Phase 2: Frontend UI Components |
| 44 | +1. Create filter UI components |
| 45 | +2. Add pagination controls |
| 46 | +3. Wire query parameters to API calls |
| 47 | +4. Update state management for filters |
| 48 | +5. Improve UX with loading states |
| 49 | + |
| 50 | +### Phase 3: Testing |
| 51 | +1. Add backend unit tests for filters |
| 52 | +2. Add integration tests for catalog endpoint |
| 53 | +3. Test pagination with various filter combinations |
| 54 | +4. Test UI responsiveness and error handling |
| 55 | + |
| 56 | +## Changes Made |
| 57 | + |
| 58 | +### Backend Changes |
| 59 | + |
| 60 | +#### Models (app/models/roadmap.py) |
| 61 | +- **CatalogFilters**: Pydantic model for filter parameters |
| 62 | + - `language`: Optional[str] - Filter by programming language |
| 63 | + - `tag`: Optional[str] - Filter by topic/tag |
| 64 | + - `difficulty`: Optional[str] - Filter by difficulty level (beginner/intermediate/advanced) |
| 65 | + - `min_rating`: Optional[float] - Minimum average rating (1.0-5.0) |
| 66 | + - `min_views`: Optional[int] - Minimum view count |
| 67 | + - `min_syncs`: Optional[int] - Minimum sync count |
| 68 | + - `sort`: str - Sort order (newest, most_viewed, most_synced, highest_rated, trending) |
| 69 | + - `page`: int - Page number (default 1) |
| 70 | + - `page_size`: int - Items per page (default 20, max 100) |
| 71 | + |
| 72 | +- **CatalogPage**: Pydantic model for paginated response |
| 73 | + - `items`: list[RoadmapResponse] - List of roadmap items for current page |
| 74 | + - `page`: int - Current page number |
| 75 | + - `page_size`: int - Number of items per page |
| 76 | + - `total_count`: int - Total number of items matching filters |
| 77 | + - `total_pages`: int - Total number of pages |
| 78 | + |
| 79 | +#### Repository Layer (app/services/roadmap_repository.py) |
| 80 | +- **RoadmapResultStore.list_catalog()**: |
| 81 | + - Queries GeneratedRoadmap table with is_public=True |
| 82 | + - Applies filters using JSON field queries: |
| 83 | + - Language: `repo_summary["language"].astext == language` |
| 84 | + - Tag: `repo_summary["topics"].contains([tag])` |
| 85 | + - Difficulty: `repo_summary["difficulty"].astext == difficulty` |
| 86 | + - Min rating: Calculates avg_rating and filters >= min_rating |
| 87 | + - Min views: `repo_summary["view_count"].astext.cast(Integer) >= min_views` |
| 88 | + - Min syncs: `repo_summary["sync_count"].astext.cast(Integer) >= min_syncs` |
| 89 | + |
| 90 | + - Implements 5 sorting options: |
| 91 | + - **newest**: ORDER BY created_at DESC |
| 92 | + - **most_viewed**: ORDER BY view_count DESC |
| 93 | + - **most_synced**: ORDER BY sync_count DESC |
| 94 | + - **highest_rated**: ORDER BY (rating_sum / rating_count) DESC |
| 95 | + - **trending**: ORDER BY (view_count * 0.4 + sync_count * 0.3 + avg_rating * 6 * 0.3) DESC |
| 96 | + |
| 97 | + - Pagination: Uses offset/limit based on page and page_size |
| 98 | + - Returns: tuple of (list[RoadmapResponse], total_count) |
| 99 | + |
| 100 | +#### Service Layer (app/services/roadmap_service.py) |
| 101 | +- **RoadmapService.list_catalog()**: |
| 102 | + - Simple wrapper that passes all parameters to repository store |
| 103 | + - Returns: tuple of (list[RoadmapResponse], total_count) |
| 104 | + |
| 105 | +#### API Layer (app/api/roadmap.py) |
| 106 | +- **GET /catalog**: Updated to accept filter query parameters |
| 107 | + - Query Parameters: |
| 108 | + - page: int (default 1, >= 1) |
| 109 | + - page_size: int (default 20, 1-100) |
| 110 | + - language: Optional[str] |
| 111 | + - tag: Optional[str] |
| 112 | + - difficulty: Optional[str] |
| 113 | + - min_rating: Optional[float] (1.0-5.0) |
| 114 | + - min_views: Optional[int] (>= 0) |
| 115 | + - min_syncs: Optional[int] (>= 0) |
| 116 | + - sort: str (default "newest") |
| 117 | + |
| 118 | + - Response: CatalogPage with items, pagination metadata |
| 119 | + - Calculates total_pages: math.ceil(total_count / page_size) |
| 120 | + |
| 121 | +#### Tests (tests/test_catalog_filters.py) |
| 122 | +Created comprehensive test suite with 22 test cases: |
| 123 | + |
| 124 | +**TestCatalogFilters** (8 tests): |
| 125 | +- test_list_catalog_no_filters: Basic listing without filters |
| 126 | +- test_list_catalog_language_filter: Filter by programming language |
| 127 | +- test_list_catalog_tag_filter: Filter by topic tag |
| 128 | +- test_list_catalog_difficulty_filter: Filter by difficulty |
| 129 | +- test_list_catalog_min_rating_filter: Filter by minimum rating |
| 130 | +- test_list_catalog_min_views_filter: Filter by minimum views |
| 131 | +- test_list_catalog_min_syncs_filter: Filter by minimum syncs |
| 132 | +- test_list_catalog_combined_filters: Multiple filters combined |
| 133 | + |
| 134 | +**TestCatalogSorting** (5 tests): |
| 135 | +- test_sort_by_newest: Verify created_at DESC ordering |
| 136 | +- test_sort_by_most_viewed: Verify view_count DESC ordering |
| 137 | +- test_sort_by_most_synced: Verify sync_count DESC ordering |
| 138 | +- test_sort_by_highest_rated: Verify avg_rating DESC ordering |
| 139 | +- test_sort_by_trending: Verify trending score calculation and ordering |
| 140 | + |
| 141 | +**TestCatalogPagination** (5 tests): |
| 142 | +- test_pagination_first_page: First page results |
| 143 | +- test_pagination_second_page: Second page has different items |
| 144 | +- test_pagination_beyond_last_page: Empty results beyond last page |
| 145 | +- test_pagination_page_size_consistency: Page size is respected |
| 146 | +- test_pagination_total_count_consistency: Total count consistent across pages |
| 147 | + |
| 148 | +**TestCatalogEdgeCases** (4 tests): |
| 149 | +- test_empty_results: Handle empty result sets |
| 150 | +- test_invalid_page_number: Handle edge case page numbers |
| 151 | +- test_min_rating_boundary: Boundary values for min_rating |
| 152 | +- test_filter_with_sorting: Filters work correctly with sorting |
| 153 | + |
| 154 | +#### Code Quality |
| 155 | +- ✅ All flake8 linting issues resolved |
| 156 | +- ✅ Code formatted with black |
| 157 | +- ✅ Imports sorted with isort --profile black |
| 158 | +- ✅ Line length within 88 character limit |
| 159 | +- ✅ No whitespace issues |
| 160 | + |
| 161 | +### Frontend Changes |
| 162 | + |
| 163 | +#### API Service (lib/services/repos.ts) |
| 164 | +- **Updated Type Definitions**: |
| 165 | + - Added `CatalogFilters` type with 9 optional filter parameters |
| 166 | + - Added `CatalogPage` type matching backend pagination response |
| 167 | + - Includes: items[], page, page_size, total_count, total_pages |
| 168 | + |
| 169 | +- **Updated listCatalog() Method**: |
| 170 | + - Now accepts optional `CatalogFilters` parameter |
| 171 | + - Builds URL query string from filter parameters |
| 172 | + - Returns `CatalogPage` instead of array |
| 173 | + - Maintains backward compatibility with no filters |
| 174 | + |
| 175 | +#### Provider (components/providers/roadmap-catalog-provider.tsx) |
| 176 | +- **Updated Data Fetching**: |
| 177 | + - Modified to handle `CatalogPage.items` response structure |
| 178 | + - Extracts items from paginated response |
| 179 | + - Maintains existing synced/pending/loading state management |
| 180 | + |
| 181 | +#### Search Page (app/search/page.tsx) |
| 182 | +- **Current State**: Using existing provider data |
| 183 | +- **Future Enhancements** (not implemented yet): |
| 184 | + - Add filter UI components for all 6 filter types |
| 185 | + - Add pagination controls (page navigation) |
| 186 | + - Wire filters to URL query params |
| 187 | + - Add sorting dropdown |
| 188 | + - Show total results count |
| 189 | + - Add loading/empty states for filtered results |
| 190 | + |
| 191 | +**Note**: The search page currently uses the existing provider pattern which now supports the paginated API. Advanced filtering UI (dropdowns, sliders, etc.) can be added as a future enhancement. |
| 192 | + |
| 193 | +### Database Changes |
| 194 | +- No schema changes required (using existing columns from previous prompts) |
| 195 | + |
| 196 | +## Testing Strategy |
| 197 | +- Unit tests for filter logic |
| 198 | +- Integration tests for catalog endpoint |
| 199 | +- End-to-end tests for search page |
| 200 | +- Manual testing of UX flow |
| 201 | + |
| 202 | +## Notes |
| 203 | +- Leveraging existing metadata columns added in Prompt 1 |
| 204 | +- No social features or complex tracking |
| 205 | +- Focus on clean, simple catalog/search experience |
0 commit comments