-
Notifications
You must be signed in to change notification settings - Fork 49
app-catalog: enchance release management with filter and bulk action #474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
app-catalog: enchance release management with filter and bulk action #474
Conversation
Signed-off-by: Quentin Lafond <[email protected]>
03e1914 to
52f8756
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR enhances the app-catalog plugin's release management interface by adding comprehensive filtering, bulk operations, and inline quick actions. Users can now efficiently manage Helm releases through a more streamlined interface without constantly navigating to detail pages.
Key Changes:
- Added smart filtering with debounced name search and namespace autocomplete
- Implemented bulk delete operations with multi-selection and confirmation dialogs
- Created inline quick actions menu for upgrade, rollback, and delete operations directly from the list view
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
ReleaseFilters.tsx |
New component providing debounced name search and namespace filtering with autocomplete |
ReleaseActionsMenu.tsx |
New component implementing three-dot menu for inline release actions (upgrade, rollback, delete) |
RollbackDialog.tsx |
New component showing release version history with timestamps for rollback selection |
DeleteConfirmDialog.tsx |
New component for single release deletion confirmation |
BulkDeleteDialog.tsx |
New component for confirming bulk deletion of multiple releases |
BulkActionsToolbar.tsx |
New component displaying selection count and bulk action buttons |
List.tsx |
Major refactoring to integrate filtering, bulk operations, inline actions, and proper state management for all operations |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } else { | ||
| enqueueSnackbar(`Successfully deleted release ${name}`, { variant: 'success' }); | ||
| setOpenDeleteAlert(false); | ||
| setIsDeleting(false); | ||
| setUpdate(prev => !prev); | ||
| } |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak: The delete status polling uses a recursive setTimeout pattern but only clears the timeout in the error case and on component unmount. If a user triggers a delete and then closes the delete dialog before the polling completes, the polling will continue indefinitely. The success case at line 213 should also check and clear the timeout before proceeding.
| } | ||
| }); | ||
| }, | ||
| [enqueueSnackbar] |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The checkDeleteReleaseStatus callback is missing update and setUpdate from its dependency array. When this callback updates state via setUpdate(prev => !prev) on line 215, it's using a closure over stale values. While the functional update pattern prevents issues with update itself, the callback should still include all its dependencies for correctness.
| [enqueueSnackbar] | |
| [enqueueSnackbar, update, setUpdate] |
| const handleConfirmBulkDelete = useCallback(() => { | ||
| if (selectedReleases.size === 0 || !releases) return; | ||
|
|
||
| setIsBulkDeleting(true); | ||
| const releasesToDelete = Array.from(selectedReleases).map(key => { | ||
| const [namespace, name] = key.split('/'); | ||
| return { namespace, name }; | ||
| }); | ||
|
|
||
| Promise.all(releasesToDelete.map(({ namespace, name }) => deleteRelease(namespace, name))) | ||
| .then(() => { | ||
| enqueueSnackbar( | ||
| `Successfully initiated deletion of ${releasesToDelete.length} release(s)`, | ||
| { variant: 'info' } | ||
| ); | ||
| setOpenBulkDeleteAlert(false); | ||
| setSelectedReleases(new Set()); | ||
| setIsBulkDeleting(false); | ||
| setUpdate(prev => !prev); | ||
| }) | ||
| .catch(error => { | ||
| console.error('Failed to delete releases:', error); | ||
| enqueueSnackbar('Failed to delete some releases', { variant: 'error' }); | ||
| setIsBulkDeleting(false); | ||
| }); | ||
| }, [selectedReleases, releases, enqueueSnackbar]); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handleConfirmBulkDelete callback has releases in its dependency array, but it's not actually used in the function body. The callback only needs selectedReleases and enqueueSnackbar which are already included. Remove releases from the dependency array to prevent unnecessary callback recreations.
| <Select | ||
| value={revertVersion} | ||
| onChange={event => onVersionChange(event.target.value as string)} | ||
| id="revert" |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing ARIA label for the Select component. The InputLabel with id="revert" is used, but the Select component should have an labelId="revert" prop to properly associate it with the label for screen readers.
| id="revert" | |
| id="revert" | |
| labelId="revert" |
| const BUTTON_STYLES = { | ||
| backgroundColor: '#000', | ||
| color: 'white', | ||
| textTransform: 'none' as const, | ||
| }; |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inline styles for the buttons use hardcoded colors ('#000' for backgroundColor) which may not respect the user's theme preferences (light/dark mode). Consider using theme colors from Material-UI instead, such as theme.palette.primary.main or defining these styles to work with the theme system.
| </DialogContentText> | ||
| </DialogContent> | ||
| <DialogActions> | ||
| <Button onClick={onClose}>{isDeleting ? 'Close' : 'No'}</Button> |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bulk delete confirmation dialog doesn't disable the close/cancel button during deletion, while the single delete dialog does (line 27 shows "Close" vs "No" based on isDeleting). This inconsistency could confuse users. Consider disabling or hiding the cancel button during bulk deletion to match the single delete behavior.
| <Button onClick={onClose}>{isDeleting ? 'Close' : 'No'}</Button> | |
| <Button disabled={isDeleting} onClick={onClose}>No</Button> |
| <Checkbox | ||
| size="small" | ||
| checked={ | ||
| filteredReleases | ||
| ? filteredReleases.length > 0 && | ||
| selectedReleases.size === filteredReleases.length | ||
| : false | ||
| } | ||
| indeterminate={ | ||
| filteredReleases | ||
| ? selectedReleases.size > 0 && selectedReleases.size < filteredReleases.length | ||
| : false | ||
| } | ||
| onChange={handleSelectAll} | ||
| /> |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The checkbox for selecting all releases should have an aria-label to describe its purpose for screen reader users. Add aria-label="Select all releases" to improve accessibility.
| <Checkbox | ||
| size="small" | ||
| checked={isSelected} | ||
| onChange={() => handleSelectRelease(release.name, release.namespace)} | ||
| /> |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each checkbox in the release rows should have an aria-label that identifies which release it's for. Consider adding aria-label={Select ${release.name}} to improve screen reader experience.
| const handleConfirmDelete = useCallback(() => { | ||
| if (selectedRelease) { | ||
| deleteRelease(selectedRelease.namespace, selectedRelease.name) | ||
| .then(() => { | ||
| setIsDeleting(true); | ||
| enqueueSnackbar(`Delete request for release ${selectedRelease.name} accepted`, { | ||
| variant: 'info', | ||
| }); | ||
| setOpenDeleteAlert(false); | ||
| checkDeleteReleaseStatus(selectedRelease.name, selectedRelease.namespace); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Race condition: If the user performs multiple delete operations in quick succession, the deleteStatusTimeoutRef will be overwritten, potentially causing multiple polling loops to run simultaneously. The code should clear any existing timeout before starting a new one in the handleConfirmDelete function.
| .catch(error => { | ||
| console.error('Failed to fetch releases:', error); | ||
| enqueueSnackbar('Failed to load releases', { variant: 'error' }); | ||
| setReleases([]); | ||
| return; | ||
| }); |
Copilot
AI
Jan 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for failed release fetching sets releases to an empty array but doesn't reset latestMap. This could leave stale version information displayed if a previous successful fetch populated the map. Consider also clearing latestMap when fetch fails, or at least handle the case where releases array is empty.
|
@Quent1L This is really awesome. Please give us some time to look through it :) |
Enhanced Release Management for App-Catalog Plugin
Overview
This PR significantly improves the user experience for managing Helm releases in the app-catalog plugin by adding filtering, bulk operations, and quick actions directly from the list view.
Key Features
Smart Filtering
Bulk Delete Operations
Promise.allfor efficient bulk operationsQuick Actions Menu
User Impact
Before: Users had to navigate to each release's detail page to perform actions, with no way to filter or bulk manage releases. The rollback dialog only showed version numbers without timestamps.
After: Users can filter hundreds of releases instantly, perform bulk deletions, access all common actions with 2 clicks instead of 4+, and easily identify deployment versions by their timestamps when rolling back.
Testing Considerations
Screenshots