Refactor HelmAppList to use standardized Table component#3057
Refactor HelmAppList to use standardized Table component#3057
Conversation
Co-authored-by: Elessar1802 <66767648+Elessar1802@users.noreply.github.com>
Co-authored-by: Elessar1802 <66767648+Elessar1802@users.noreply.github.com>
Co-authored-by: Elessar1802 <66767648+Elessar1802@users.noreply.github.com>
Co-authored-by: Elessar1802 <66767648+Elessar1802@users.noreply.github.com>
|
There was a problem hiding this comment.
Pull request overview
Refactors the Helm app listing to use the standardized Table component from @devtron-labs/devtron-fe-common-lib, aligning HelmAppList with the approach used by GenericAppList and consolidating table rendering concerns (columns, cells, empty states, wrappers).
Changes:
- Replaced manual Helm table rendering in
HelmAppList.tsxwith<Table>using column definitions + row transformation + row-click navigation. - Added Helm-specific
CellComponents and aViewWrapperto keep custom UI (message strips, error strips, guided cards) around the Table. - Introduced Helm table column factory (
getHelmAppListColumns) and new row/additional-props types.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/app/list-new/HelmAppList.tsx | Switches Helm list rendering to standardized Table, adds row mapping, filter, onRowClick, wrapper usage, and empty-state handling. |
| src/components/app/list-new/HelmAppListCellComponents.tsx | Adds custom cell renderers for name/icon, status, environment, and last deployed timestamp. |
| src/components/app/list-new/HelmAppListViewWrapper.tsx | Wraps the Table view with cluster-select note, fetch error strips, and guided content cards. |
| src/components/app/list-new/list.utils.ts | Adds getHelmAppListColumns() to define Helm columns (including sortable ones and conditional status column). |
| src/components/app/list-new/AppListType.ts | Adds HelmAppListRowType and HelmAppListAdditionalProps types for the new Table integration. |
| src/components/app/list-new/styles.scss | Adds table-specific styling for table__helm-app-list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| title: APP_LIST_EMPTY_STATE_MESSAGING.noHelmChartsFound, | ||
| subTitle: APP_LIST_EMPTY_STATE_MESSAGING.connectClusterInfoText, |
There was a problem hiding this comment.
getNoRowsForFilterConfig returns the "No helm charts found / Connect a cluster" messaging for the general filtered-empty case. Previously (and in GenericAppList) filtered-empty states use the "No apps found" messaging with a clear-filters CTA; the current config can mislead users into thinking they must connect a cluster when the issue is restrictive filters/search. Consider returning APP_LIST_EMPTY_STATE_MESSAGING.noAppsFound/noAppsFoundInfoText (and relying on clearFilters) for non-"all clusters" filter cases.
| title: APP_LIST_EMPTY_STATE_MESSAGING.noHelmChartsFound, | |
| subTitle: APP_LIST_EMPTY_STATE_MESSAGING.connectClusterInfoText, | |
| title: APP_LIST_EMPTY_STATE_MESSAGING.noAppsFound, | |
| subTitle: APP_LIST_EMPTY_STATE_MESSAGING.noAppsFoundInfoText, |
| if (project.length) { | ||
| const projectMap = new Map<string, true>(project.map((projectId) => [projectId, true])) | ||
| isMatch = isMatch && (projectMap.get(String(app.detail.projectId)) ?? false) | ||
| } |
There was a problem hiding this comment.
The table filter callback creates new Maps (e.g., for project) during each row evaluation. Since the filter runs once per row, this can add significant overhead on large lists. Consider precomputing Set/Map lookups via useMemo and using them inside the callback.
| filtersVariant={FiltersTypeEnum.URL} | ||
| paginationVariant={PaginationEnum.PAGINATED} | ||
| emptyStateConfig={{ |
There was a problem hiding this comment.
With the switch to <Table>, pagination is always enabled even while external apps are still streaming via SSE. Previously pagination was disabled while fetchingExternalApps was true to avoid confusing empty pages/jumping results during data population. Consider disabling pagination (or forcing a loading state) while sseConnection/fetchingExternalApps is active to preserve the earlier UX.
| * limitations under the License. | ||
| */ | ||
|
|
||
| import { ComponentSizeType, DocLink, FiltersTypeEnum, TableViewWrapperProps, URLS as CommonURLS } from '@devtron-labs/devtron-fe-common-lib' |
There was a problem hiding this comment.
URLS as CommonURLS is imported from @devtron-labs/devtron-fe-common-lib but never used in this file. Consider removing the unused import to keep the module clean and avoid potential lint/noUnusedLocals failures.
| import { ComponentSizeType, DocLink, FiltersTypeEnum, TableViewWrapperProps, URLS as CommonURLS } from '@devtron-labs/devtron-fe-common-lib' | |
| import { ComponentSizeType, DocLink, FiltersTypeEnum, TableViewWrapperProps } from '@devtron-labs/devtron-fe-common-lib' |
| <p className="dc__truncate-text m-0" data-testid={`${app.environmentDetail.environmentName}-environment`}> | ||
| {app.environmentDetail.environmentName | ||
| ? app.environmentDetail.environmentName | ||
| : `${app.environmentDetail.clusterName}__${app.environmentDetail.namespace}`} | ||
| </p> |
There was a problem hiding this comment.
The data-testid here is based on environmentName, which can be empty (the UI falls back to cluster__namespace). This can produce unstable/empty test ids (e.g., "-environment"). Consider using a stable identifier (like environmentId when present, or clusterName__namespace) for data-testid.
| import { ReactComponent as CloseIcon } from '../../../assets/icons/ic-close.svg' | ||
| import { ReactComponent as AlertTriangleIcon } from '../../../assets/icons/ic-alert-triangle.svg' | ||
| import { ReactComponent as ArrowRight } from '../../../assets/icons/ic-arrow-right.svg' | ||
| import noChartInClusterImage from '../../../assets/img/ic-no-chart-in-clusters@2x.png' |
There was a problem hiding this comment.
noChartInClusterImage is imported but no longer used after switching to the Table empty-state configs (the image is now referenced via imgName). Consider removing this unused import to avoid dead code and potential lint/noUnusedLocals failures.
| import noChartInClusterImage from '../../../assets/img/ic-no-chart-in-clusters@2x.png' |
| isMatch && | ||
| (app.detail.appName.toLowerCase().includes(searchLowerCase) || | ||
| app.detail.chartName.toLowerCase().includes(searchLowerCase)) |
There was a problem hiding this comment.
This use of variable 'isMatch' always evaluates to true.
| isMatch && | |
| (app.detail.appName.toLowerCase().includes(searchLowerCase) || | |
| app.detail.chartName.toLowerCase().includes(searchLowerCase)) | |
| app.detail.appName.toLowerCase().includes(searchLowerCase) || | |
| app.detail.chartName.toLowerCase().includes(searchLowerCase) |
|
|
||
| // Filter function for the table | ||
| const filter = useCallback( | ||
| ({ data: app }) => { |
There was a problem hiding this comment.
Is type of app available?
| [searchKey, project, environment, namespace], | ||
| ) | ||
|
|
||
| const onRowClick = useCallback(({ data: helmApp }) => { |
| ) | ||
|
|
||
| const onRowClick = useCallback(({ data: helmApp }) => { | ||
| const buildAppDetailUrl = (app: HelmApp) => { |
There was a problem hiding this comment.
We can move this to util this is a pure method
| } | ||
|
|
||
| filteredHelmAppList = filteredHelmAppList.slice(offset, offset + pageSize) | ||
| function _isAnyFilterationAppliedExceptClusterAndNs() { |
There was a problem hiding this comment.
These can be arrow methods
| const _removeExternalAppFetchError = (index: number) => { | ||
| const _externalHelmListFetchErrors = [...externalHelmListFetchErrors] | ||
| _externalHelmListFetchErrors.splice(index, 1) | ||
| setExternalHelmListFetchErrors(_externalHelmListFetchErrors) |
There was a problem hiding this comment.
We can directly use callback from setState and filter method
| }, [push]) | ||
|
|
||
| const filteredListTotalSize = filteredHelmAppList.length | ||
| const _removeExternalAppFetchError = (index: number) => { |
There was a problem hiding this comment.
Why these methods are starting with _?
|
|
||
| filteredHelmAppList = filteredHelmAppList.slice(offset, offset + pageSize) | ||
| function _isAnyFilterationAppliedExceptClusterAndNs() { | ||
| return project.length || searchKey.length || environment.length || appStatus.length |
There was a problem hiding this comment.
Pls make it boolean if return type is intended as boolean, same for below method
| * Returns null to indicate that the empty state should be handled externally (outside the Table component). | ||
| * The external handling is done in the conditional rendering below. | ||
| */ | ||
| const getNoRowsConfig = () => { |
There was a problem hiding this comment.
This method is returning null every time
| return ( | ||
| <> | ||
| <div className="app-list__cell--icon"> | ||
| <LazyImage |
There was a problem hiding this comment.
There is a component called ImageWithFallback can use that
| } | ||
|
|
||
| return ( | ||
| <Tippy |
| </div> | ||
| <div className="app-list__cell app-list__cell--name flex column left"> | ||
| <div className="dc__truncate-text m-0 value cb-5">{app.appName}</div> | ||
| <div className="dc__truncate-text fs-12 m-0">{app.chartName}</div> |
There was a problem hiding this comment.
Check if tooltip is required in any case, pls check all cell components
| <ErrorExclamationIcon className="icon-dim-20" /> | ||
| </span> | ||
| <span>{externalHelmListFetchError}</span> | ||
| <CloseIcon |
There was a problem hiding this comment.
Please use Button and move onClick to a handler
| <div className="h-8" /> | ||
| <div className="cluster-select-message-strip above-header-message flex left"> | ||
| <span className="mr-8 flex"> | ||
| <InfoFillPurple className="icon-dim-20" /> |
There was a problem hiding this comment.
There must be a Icon name present for this, check across this fill



Description
Replaces custom table implementation in
HelmAppList.tsxwith standardizedTablecomponent from@devtron-labs/devtron-fe-common-lib, following the pattern established in GenericAppList (PR #3042).Changes
New files:
HelmAppListCellComponents.tsx- Custom cell renderers for app name (with icon + chart name), status, environment, and last deployed timestampHelmAppListViewWrapper.tsx- Wrapper for cluster selection messages, error strips, and guided content cardsModified files:
HelmAppList.tsx- Removed ~200 lines of manual table rendering (headers, rows, pagination, shimmer loading). Replaced with<Table>component, client-side filter function, and row click handler. Dynamically disables column sorting during SSE connection.list.utils.ts- AddedgetHelmAppListColumns()with sortable columns (APP_NAME, LAST_DEPLOYED) and conditional AppStatus columnAppListType.ts- AddedHelmAppListRowTypeandHelmAppListAdditionalPropstypesstyles.scss- Added styles for#table-wrapper-table__helm-app-listPreserved functionality:
Type of change
How Has This Been Tested?
Checklist:
Original prompt
Problem
The file
src/components/app/list-new/HelmAppList.tsxuses a custom table implementation with manual header rendering, sorting, pagination, sticky headers, shimmer loading, and row rendering. This should be replaced with the standardizedTablecomponent from@devtron-labs/devtron-fe-common-lib.Current Implementation
The current
HelmAppList.tsxat commit8eb63524e15159c952dfec199bdb5c48729f3555has:renderHeaders()withSortableTableHeaderCell,useStickyEvent,getClassNameForStickyHeaderWithShadowrenderHelmAppLink()rendering each row as a<Link>with inline layoutuseMemofor filtering bysearchKey,project,environment,namespaceand sorting byAppListSortableKeys.APP_NAMEorAppListSortableKeys.LAST_DEPLOYEDPaginationcomponent usageappListLoadingArrayaskToSelectClusterId,askToClearFiltersWithSelectClusterTip,askToConnectAClusterForNoResult,renderAllCheckModal)handleSorting,changePage,changePageSizefrom parentfilterConfigRequirements
Replace the custom table with the
Tablecomponent following the patterns established in these reference PRs:list-newdirectory)Specific Changes Needed
Define table columns in a utility file or constants file (can be in
list.utils.tsor a new file). Columns should include:AppListSortableKeys.APP_NAME, needs a customCellComponentthat renders the app icon viaLazyImage, the app name, and the chart name below itisArgoInstalledis true) — usesAppStatuscomponentENVIRONMENT_HEADER_TIPPY_CONTENT), showsenvironmentNameorclusterName__namespaceclusterNamenamespaceAppListSortableKeys.LAST_DEPLOYED, needs a customCellComponentthat showshandleUTCTimewith a Tippy tooltip showing the full dateTransform HelmApp data into table rows using
useMemo:Create CellComponents in a new file (e.g.,
HelmAppListCellComponents.tsx):HelmAppNameCellComponent— rendersLazyImageicon + app name + chart nameHelmAppStatusCellComponent— renders<AppStatus>HelmAppEnvironmentCellComponent— renders environment name or cluster__namespace fallbackHelmAppLastDeployedCellComponent— rendershandleUTCTimewith Tippy tooltipCreate a filter function (memoized with
useCallback):searchKey(app name or chart name includes search)project(matchingprojectId)environment(matchingenvironmentId)namespace(matchingclusterId_namespace)Handle the
onRowClickcallback to navigate to the app detail URL (using_buildAppDetailUrllogic). Wrap withuseCallback.Use
FiltersTypeEnum.URLsince the parent already manages URL-based filters, but NOTE: The Table component will manage its own URL filters internally. The parent component passesfilterConfigwhich includessortBy,sortOrder,offset,pageSizeetc. Since HelmAppList uses FE-only data (therowsprop, notgetRows), userowsprop withfilterfunction. The Table component will handle sorting via columncomparatorfunctions and pagination internally.Handle conditional rendering outside the Table:
askToSelectClusterId,renderHelmPermissionMessageStrip, etc.) should be rendered OUTSIDE the Table when appropriateclusterIdsCsvcheck, SSE connection logic, and data fetching should remain as-isshowGuidedContentCards) can be handled via aViewWrappercomponentCreate a
ViewWrappercomponent that renders:!clusterIdsCsv)showGuidedContentCards){children}(table content)Add styles in a new
styles.scssfile with selector ...This pull request was created from Copilot chat.
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.