Skip to content

Conversation

@Quent1L
Copy link

@Quent1L Quent1L commented Dec 26, 2025

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

  • Name search: Debounced text input for searching releases by name (300ms delay for optimal performance)
  • Namespace filter: Autocomplete dropdown dynamically populated from available releases
  • Combined filtering: Both filters work together for precise results
  • Consistent UX: Filters positioned in the header, matching the Charts list design

Bulk Delete Operations

  • Multi-selection: Checkbox column for selecting individual or all releases
  • Selection toolbar: Displays count of selected items with a delete button
  • Smart "Select All": Works with filtered results, supports indeterminate state
  • Confirmation dialog: Prevents accidental bulk deletions with clear messaging
  • Parallel deletion: Uses Promise.all for efficient bulk operations

Quick Actions Menu

  • Inline actions: Three-dot menu on each row for common operations
  • Upgrade: Opens editor dialog without navigating to detail page
  • Rollback: Shows version history with dates/times, automatically disabled for version 1
  • Delete: Single-click access to delete confirmation
  • Consistent behavior: Reuses all logic from the detail page for reliability

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

  • Verify filtering works correctly with special characters in names
  • Test bulk delete with mix of successful/failed deletions
  • Confirm rollback dialog shows correct version history
  • Check "Select All" behavior with filters applied
  • Validate timeout cleanup prevents memory leaks

Screenshots

image

@Quent1L Quent1L force-pushed the feature/app-catalog-release-filter branch from 03e1914 to 52f8756 Compare December 26, 2025 18:42
@illume illume requested a review from Copilot January 5, 2026 21:39
Copy link

Copilot AI left a 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.

Comment on lines +211 to +216
} else {
enqueueSnackbar(`Successfully deleted release ${name}`, { variant: 'success' });
setOpenDeleteAlert(false);
setIsDeleting(false);
setUpdate(prev => !prev);
}
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
}
});
},
[enqueueSnackbar]
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
[enqueueSnackbar]
[enqueueSnackbar, update, setUpdate]

Copilot uses AI. Check for mistakes.
Comment on lines +299 to +324
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]);
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
<Select
value={revertVersion}
onChange={event => onVersionChange(event.target.value as string)}
id="revert"
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
id="revert"
id="revert"
labelId="revert"

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +19
const BUTTON_STYLES = {
backgroundColor: '#000',
color: 'white',
textTransform: 'none' as const,
};
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>{isDeleting ? 'Close' : 'No'}</Button>
Copy link

Copilot AI Jan 5, 2026

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.

Suggested change
<Button onClick={onClose}>{isDeleting ? 'Close' : 'No'}</Button>
<Button disabled={isDeleting} onClick={onClose}>No</Button>

Copilot uses AI. Check for mistakes.
Comment on lines +379 to +393
<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}
/>
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +400 to +404
<Checkbox
size="small"
checked={isSelected}
onChange={() => handleSelectRelease(release.name, release.namespace)}
/>
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +239
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);
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +122
.catch(error => {
console.error('Failed to fetch releases:', error);
enqueueSnackbar('Failed to load releases', { variant: 'error' });
setReleases([]);
return;
});
Copy link

Copilot AI Jan 5, 2026

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.

Copilot uses AI. Check for mistakes.
@illume
Copy link
Contributor

illume commented Jan 6, 2026

@Quent1L This is really awesome. Please give us some time to look through it :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants