✨ Merge Project Selector and Filter Panel into single dropdown#4369
✨ Merge Project Selector and Filter Panel into single dropdown#4369clubanderson merged 1 commit intomainfrom
Conversation
The navbar had two overlapping filter dropdowns — "All Projects" and the funnel filter — that confused users. The Project Selector let users create named cluster groupings with colors, but no card components actually consumed its filter (useProjectFilter was a dead code path). This merges both into a single unified filter dropdown: - Projects section (with color dots) at the top of the filter dropdown, replacing the old Cluster Groups section - Color picker in the create form (6 colors) - Active group detection — trigger button shows the group's color dot and name when a saved grouping matches the current cluster selection - One-time migration of legacy projects:definitions localStorage data into globalFilter:clusterGroups - Removes ProjectSelector, useProjectFilter, and projects.ts (all dead) - Removes ProjectFilterProvider from App.tsx provider tree All existing filter behavior (severity, status, text search, individual clusters) is preserved. The 80+ card components that consume useGlobalFilters are unaffected. Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
👋 Hey @clubanderson — thanks for opening this PR!
This is an automated message. |
✅ Deploy Preview for kubestellarconsole ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
This PR consolidates the previously separate (and partially unused) “All Projects” selector and the existing filter panel into a single unified navbar dropdown, using the existing cluster group mechanism as the “Projects” concept and migrating legacy project definitions from localStorage.
Changes:
- Removes the unused
useProjectFilter+ProjectSelectorstack and its provider wiring from the app. - Extends the existing
ClusterFilterPanelto display/manage “Projects” (cluster groups) including color selection and active-group detection in the trigger. - Adds a one-time migration that converts legacy
projects:definitionslocalStorage data intoglobalFilter:clusterGroups.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/lib/projects.ts | Deletes legacy project model/constants and storage keys. |
| web/src/hooks/useProjectFilter.tsx | Removes the unused project filter context/provider/hook. |
| web/src/hooks/useGlobalFilters.tsx | Adds legacy project → cluster group migration during initialization. |
| web/src/components/layout/navbar/ProjectSelector.tsx | Deletes the old “All Projects” navbar dropdown component. |
| web/src/components/layout/navbar/Navbar.tsx | Removes the extra navbar dropdown, leaving the unified filter panel. |
| web/src/components/layout/navbar/ClusterFilterPanel.tsx | Adds “Projects” section, color picker, and active-group trigger label/dot. |
| web/src/App.tsx | Removes ProjectFilterProvider from the provider tree. |
| let groups: ClusterGroup[] = DEFAULT_GROUPS | ||
| try { | ||
| const stored = localStorage.getItem(GROUPS_STORAGE_KEY) | ||
| if (stored) { | ||
| return JSON.parse(stored) | ||
| groups = JSON.parse(stored) |
There was a problem hiding this comment.
groups is initialized to DEFAULT_GROUPS by reference. If the migration below pushes into groups, it will mutate the module-level DEFAULT_GROUPS array. Initialize with a fresh array (e.g., a shallow copy) to avoid shared mutable state.
| const existingNames = new Set(groups.map(g => g.name)) | ||
| for (const p of projects) { | ||
| if (!existingNames.has(p.name)) { | ||
| groups.push({ | ||
| id: `migrated-${p.id}`, |
There was a problem hiding this comment.
In the legacy projects → groups migration, existingNames is never updated when a group is added. If the legacy list contains duplicate names, multiple groups with the same name can be migrated. Add each migrated name to existingNames when you push.
| if (Array.isArray(projects) && projects.length > 0) { | ||
| const existingNames = new Set(groups.map(g => g.name)) | ||
| for (const p of projects) { | ||
| if (!existingNames.has(p.name)) { | ||
| groups.push({ | ||
| id: `migrated-${p.id}`, | ||
| name: p.name, | ||
| clusters: p.clusters || [], | ||
| color: p.color, | ||
| }) | ||
| } | ||
| } | ||
| localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups)) | ||
| } | ||
| localStorage.removeItem('projects:definitions') | ||
| localStorage.removeItem('projects:selected') |
There was a problem hiding this comment.
These keys are removed whenever oldProjects exists, even if the parsed value wasn’t a valid non-empty array (i.e., nothing was migrated). Consider only removing projects:definitions / projects:selected after you’ve validated and/or successfully migrated the data to avoid accidental data loss.
| if (Array.isArray(projects) && projects.length > 0) { | |
| const existingNames = new Set(groups.map(g => g.name)) | |
| for (const p of projects) { | |
| if (!existingNames.has(p.name)) { | |
| groups.push({ | |
| id: `migrated-${p.id}`, | |
| name: p.name, | |
| clusters: p.clusters || [], | |
| color: p.color, | |
| }) | |
| } | |
| } | |
| localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups)) | |
| } | |
| localStorage.removeItem('projects:definitions') | |
| localStorage.removeItem('projects:selected') | |
| if (Array.isArray(projects)) { | |
| if (projects.length > 0) { | |
| const existingNames = new Set(groups.map(g => g.name)) | |
| for (const p of projects) { | |
| if (!existingNames.has(p.name)) { | |
| groups.push({ | |
| id: `migrated-${p.id}`, | |
| name: p.name, | |
| clusters: p.clusters || [], | |
| color: p.color, | |
| }) | |
| } | |
| } | |
| localStorage.setItem(GROUPS_STORAGE_KEY, JSON.stringify(groups)) | |
| } | |
| localStorage.removeItem('projects:definitions') | |
| localStorage.removeItem('projects:selected') | |
| } |
| key={c} | ||
| onClick={() => setNewGroupColor(c)} |
There was a problem hiding this comment.
The color-swatch buttons are purely visual and have no accessible name, which makes the color picker hard to use with screen readers. Add an aria-label (and optionally aria-pressed for the selected swatch) so the control is operable.
| key={c} | |
| onClick={() => setNewGroupColor(c)} | |
| key={c} | |
| type="button" | |
| onClick={() => setNewGroupColor(c)} | |
| aria-label={t('common:filters.selectColor', 'Select color {{color}}', { color: c })} | |
| aria-pressed={newGroupColor === c} |
🔄 Auto-Applying Copilot Code ReviewCopilot code review found 2 code suggestion(s) and 2 general comment(s). @copilot Please apply all of the following code review suggestions:
Also address these general comments:
Push all fixes in a single commit. Run Auto-generated by copilot-review-apply workflow. |
|
Thank you for your contribution! Your PR has been merged. Check out what's new:
Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey |
|
Post-merge build verification passed ✅ Both Go and frontend builds compiled successfully against merge commit |
Applied all 4 suggestions in commit 0e62814:
Build and lint both pass with no new errors. |
✅ Post-Merge Verification: passedCommit: |
DEFAULT_GROUPSreference mutation (useGlobalFilters.tsx:151)existingNamesnot updated in migration loop (useGlobalFilters.tsx:173)aria-label+aria-pressed+type="button"to color swatches (ClusterFilterPanel.tsx:342)