diff --git a/src/components/app/list-new/AppListType.ts b/src/components/app/list-new/AppListType.ts index 42643d8a05..7237ce0fb7 100644 --- a/src/components/app/list-new/AppListType.ts +++ b/src/components/app/list-new/AppListType.ts @@ -248,3 +248,14 @@ export interface ExportAppListDataType { export type GenericAppListRowType = { detail: GenericAppType } & Record + +export type HelmAppListRowType = { + detail: HelmApp +} & Record + +export interface HelmAppListAdditionalProps { + showGuidedContentCards: boolean + externalHelmListFetchErrors: string[] + clusterIdsCsv: string + removeExternalAppFetchError: (index: number) => void +} diff --git a/src/components/app/list-new/HelmAppList.tsx b/src/components/app/list-new/HelmAppList.tsx index 57ec62e22b..2e92bcd701 100644 --- a/src/components/app/list-new/HelmAppList.tsx +++ b/src/components/app/list-new/HelmAppList.tsx @@ -14,62 +14,42 @@ * limitations under the License. */ -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useState, useCallback } from 'react' import { - AppStatus, showError, ErrorScreenManager, ServerErrors, Host, GenericEmptyState, - DEFAULT_BASE_PAGE_SIZE, - Pagination, - handleUTCTime, - DATE_TIME_FORMATS, - SortableTableHeaderCell, - stringComparatorBySortOrder, - useStickyEvent, - getClassNameForStickyHeaderWithShadow, - DocLink, URLS as CommonURLS, - ComponentSizeType, + Table, + PaginationEnum, + FiltersTypeEnum, + TableProps, } from '@devtron-labs/devtron-fe-common-lib' -import { Link } from 'react-router-dom' -import Tippy from '@tippyjs/react' -import moment from 'moment' +import { Link, useHistory } from 'react-router-dom' import { getDevtronInstalledHelmApps } from './AppListService' -import { LazyImage } from '../../common' -import { SERVER_MODE, URLS, checkIfDevtronOperatorHelmRelease, ModuleNameMap } from '../../../config' +import { SERVER_MODE, URLS, checkIfDevtronOperatorHelmRelease } from '../../../config' import { AppListViewType } from '../config' -import { ReactComponent as ICHelpOutline } from '../../../assets/icons/ic-help-outline.svg' import NoClusterSelectImage from '../../../assets/icons/ic-select-cluster.svg' -import defaultChartImage from '../../../assets/icons/ic-default-chart.svg' -import HelmCluster from '../../../assets/img/guided-helm-cluster.png' -import DeployCICD from '../../../assets/img/guide-onboard.png' import { AllCheckModal } from '../../checkList/AllCheckModal' -import { ReactComponent as InfoFillPurple } from '../../../assets/icons/ic-info-filled-purple.svg' -import { ReactComponent as ErrorExclamationIcon } from '../../../assets/icons/ic-error-exclamation.svg' -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' -import ContentCard from '../../common/ContentCard/ContentCard' -import { CardContentDirection, CardLinkIconPlacement } from '../../common/ContentCard/ContentCard.types' import '../list/list.scss' import { APP_LIST_EMPTY_STATE_MESSAGING, - ENVIRONMENT_HEADER_TIPPY_CONTENT, EXTERNAL_HELM_APP_FETCH_CLUSTER_ERROR, EXTERNAL_HELM_APP_FETCH_ERROR, EXTERNAL_HELM_SSE_CONNECTION_ERROR, APP_LIST_HEADERS, HELM_PERMISSION_MESSAGE, - SELECT_CLUSTER_FROM_FILTER_NOTE, - appListLoadingArray, } from './Constants' -import { HELM_GUIDED_CONTENT_CARDS_TEXTS } from '../../onboardingGuide/OnboardingGuide.constants' -import { HelmAppListResponse, HelmApp, AppListSortableKeys, HelmAppListProps } from './AppListType' +import { HelmAppListResponse, HelmApp, AppListSortableKeys, HelmAppListProps, HelmAppListRowType } from './AppListType' import AskToClearFilters from './AppListComponents' +import { getHelmAppListColumns } from './list.utils' +import { HelmAppListViewWrapper } from './HelmAppListViewWrapper' + +import './styles.scss' const HelmAppList = ({ serverMode, @@ -96,53 +76,113 @@ const HelmAppList = ({ const [externalHelmListFetchErrors, setExternalHelmListFetchErrors] = useState([]) const [showGuidedContentCards, setShowGuidedContentCards] = useState(false) + const { push } = useHistory() + const { appStatus, environment, cluster, namespace, project, searchKey, sortBy, sortOrder, offset, pageSize } = filterConfig - const handleAppListSorting = (a: HelmApp, b: HelmApp) => - sortBy === AppListSortableKeys.APP_NAME - ? stringComparatorBySortOrder(a.appName, b.appName, sortOrder) - : stringComparatorBySortOrder(a.lastDeployedAt, b.lastDeployedAt, sortOrder) - - const { filteredHelmAppList, filteredListTotalSize } = useMemo(() => { - let filteredHelmAppList: HelmApp[] = [...devtronInstalledHelmAppsList, ...externalHelmAppsList] - if (searchKey) { - const searchLowerCase = searchKey.toLowerCase() - filteredHelmAppList = filteredHelmAppList.filter( - (app) => app.appName.includes(searchLowerCase) || app.chartName.includes(searchLowerCase), - ) - } - if (project.length) { - const projectMap = new Map(project.map((projectId) => [projectId, true])) - filteredHelmAppList = filteredHelmAppList.filter((app) => projectMap.get(String(app.projectId)) ?? false) - } - if (environment.length) { - const environmentMap = new Map(environment.map((envId) => [envId, true])) - filteredHelmAppList = filteredHelmAppList.filter( - (app) => environmentMap.get(String(app.environmentDetail.environmentId)) ?? false, - ) + // Transform helm apps into table rows + const rows = useMemo( + () => + [...devtronInstalledHelmAppsList, ...externalHelmAppsList].map((app) => ({ + id: app.appId, + data: { + detail: app, + [AppListSortableKeys.APP_NAME]: app.appName, + [APP_LIST_HEADERS.AppStatus]: app.appStatus, + [APP_LIST_HEADERS.Environment]: app.environmentDetail.environmentName + ? app.environmentDetail.environmentName + : `${app.environmentDetail.clusterName}__${app.environmentDetail.namespace}`, + [APP_LIST_HEADERS.Cluster]: app.environmentDetail.clusterName, + [APP_LIST_HEADERS.Namespace]: app.environmentDetail.namespace, + [AppListSortableKeys.LAST_DEPLOYED]: app.lastDeployedAt, + }, + })) as TableProps['rows'], + [devtronInstalledHelmAppsList, externalHelmAppsList], + ) + + const columns = useMemo(() => { + const cols = getHelmAppListColumns(isArgoInstalled) + // Disable sorting when SSE is fetching external apps + if (sseConnection) { + return cols.map(column => ({ + ...column, + isSortable: column.isSortable ? false : undefined, + })) } - if (namespace.length) { - const namespaceMap = new Map(namespace.map((namespaceItem) => [namespaceItem, true])) - filteredHelmAppList = filteredHelmAppList.filter( - (app) => - namespaceMap.get(`${app.environmentDetail.clusterId}_${app.environmentDetail.namespace}`) ?? false, - ) + return cols + }, [isArgoInstalled, sseConnection]) + + // Filter function for the table + const filter = useCallback( + ({ data: app }) => { + let isMatch = true + + if (searchKey) { + const searchLowerCase = searchKey.toLowerCase() + isMatch = + isMatch && + (app.detail.appName.toLowerCase().includes(searchLowerCase) || + app.detail.chartName.toLowerCase().includes(searchLowerCase)) + } + + if (project.length) { + const projectMap = new Map(project.map((projectId) => [projectId, true])) + isMatch = isMatch && (projectMap.get(String(app.detail.projectId)) ?? false) + } + + if (environment.length) { + const environmentMap = new Map(environment.map((envId) => [envId, true])) + isMatch = isMatch && (environmentMap.get(String(app.detail.environmentDetail.environmentId)) ?? false) + } + + if (namespace.length) { + const namespaceMap = new Map(namespace.map((namespaceItem) => [namespaceItem, true])) + isMatch = + isMatch && + (namespaceMap.get( + `${app.detail.environmentDetail.clusterId}_${app.detail.environmentDetail.namespace}`, + ) ?? + false) + } + + return isMatch + }, + [searchKey, project, environment, namespace], + ) + + const onRowClick = useCallback(({ data: helmApp }) => { + const buildAppDetailUrl = (app: HelmApp) => { + if (app.isExternal) { + return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.EXTERNAL_APPS}/${app.appId}/${app.appName}` + } + return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.DEVTRON_CHARTS}/deployments/${app.appId}/env/${app.environmentDetail.environmentId}` } - filteredHelmAppList = filteredHelmAppList.sort((a, b) => handleAppListSorting(a, b)) + push(buildAppDetailUrl(helmApp.detail)) + }, [push]) - const filteredListTotalSize = filteredHelmAppList.length + const _removeExternalAppFetchError = (index: number) => { + const _externalHelmListFetchErrors = [...externalHelmListFetchErrors] + _externalHelmListFetchErrors.splice(index, 1) + setExternalHelmListFetchErrors(_externalHelmListFetchErrors) + } - filteredHelmAppList = filteredHelmAppList.slice(offset, offset + pageSize) + function _isAnyFilterationAppliedExceptClusterAndNs() { + return project.length || searchKey.length || environment.length || appStatus.length + } - return { filteredHelmAppList, filteredListTotalSize } - }, [devtronInstalledHelmAppsList, externalHelmAppsList, filterConfig]) + function _isAnyFilterationApplied() { + return _isAnyFilterationAppliedExceptClusterAndNs() || cluster.length || namespace.length + } - const { stickyElementRef, isStuck: isHeaderStuck } = useStickyEvent({ - identifier: 'helm-app-list', - containerRef: appListContainerRef, - isStickyElementMounted: dataStateType === AppListViewType.LIST && filteredListTotalSize > 0, - }) + function _isOnlyAllClusterFilterationApplied() { + const _isAllClusterSelected = cluster.length === clusterList?.length + const _isAnyNamespaceSelected = !!namespace.length + return !_isAnyFilterationAppliedExceptClusterAndNs() && _isAllClusterSelected && !_isAnyNamespaceSelected + } + + const isAnyFilterApplied = _isAnyFilterationApplied() + const isOnlyAllClusterFilterApplied = _isOnlyAllClusterFilterationApplied() // component load useEffect(() => { @@ -295,237 +335,6 @@ const HelmAppList = ({ setSseConnection(_sseConnection) } - function _isAnyFilterationAppliedExceptClusterAndNs() { - return project.length || searchKey.length || environment.length || appStatus.length - } - - function _isAnyFilterationApplied() { - return _isAnyFilterationAppliedExceptClusterAndNs() || cluster.length || namespace.length - } - - function _isOnlyAllClusterFilterationApplied() { - const _isAllClusterSelected = cluster.length === clusterList?.length - const _isAnyNamespaceSelected = !!namespace.length - return !_isAnyFilterationAppliedExceptClusterAndNs() && _isAllClusterSelected && !_isAnyNamespaceSelected - } - - function handleImageError(e) { - const target = e.target as HTMLImageElement - target.onerror = null - target.src = defaultChartImage - } - - function _buildAppDetailUrl(app: HelmApp) { - if (app.isExternal) { - return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.EXTERNAL_APPS}/${app.appId}/${app.appName}` - } - return `${CommonURLS.INFRASTRUCTURE_MANAGEMENT_APP}/${URLS.DEVTRON_CHARTS}/deployments/${app.appId}/env/${app.environmentDetail.environmentId}` - } - - function _removeExternalAppFetchError(e) { - const index = Number(e.currentTarget.dataset.id) - const _externalHelmListFetchErrors = [...externalHelmListFetchErrors] - _externalHelmListFetchErrors.splice(index, 1) - setExternalHelmListFetchErrors(_externalHelmListFetchErrors) - } - - const handleAppNameSorting = () => handleSorting(AppListSortableKeys.APP_NAME) - - const handleLastDeployedSorting = () => handleSorting(AppListSortableKeys.LAST_DEPLOYED) - - function renderHeaders() { - return ( -
-
-
- {sseConnection && {APP_LIST_HEADERS.ReleaseName}} - {!sseConnection && ( - - )} -
- {isArgoInstalled && ( -
- {APP_LIST_HEADERS.AppStatus} -
- )} -
- {APP_LIST_HEADERS.Environment} - -
- -
-
-
-
- {APP_LIST_HEADERS.Cluster} -
-
- {APP_LIST_HEADERS.Namespace} -
-
- -
-
- ) - } - - const renderFetchError = (externalHelmListFetchError: string, index: number) => ( -
-
-
- - - - {externalHelmListFetchError} - -
-
- ) - - const renderHelmAppLink = (app: HelmApp): JSX.Element => ( - -
- -
-
-
{app.appName}
-
{app.chartName}
-
- {isArgoInstalled && ( -
- -
- )} -
-

- {app.environmentDetail.environmentName - ? app.environmentDetail.environmentName - : `${app.environmentDetail.clusterName}__${app.environmentDetail.namespace}`} -

-
-
-

- {app.environmentDetail.clusterName} -

-
-
-

- {app.environmentDetail.namespace} -

-
-
- {app.lastDeployedAt && ( - -

{handleUTCTime(app.lastDeployedAt, true)}

-
- )} -
- - ) - - function renderApplicationList() { - return ( -
- {!clusterIdsCsv && ( -
-
-
- - - -
- {SELECT_CLUSTER_FROM_FILTER_NOTE}  - -
-
-
- )} - {externalHelmListFetchErrors.map((externalHelmListFetchError, index) => - renderFetchError(externalHelmListFetchError, index), - )} - {filteredListTotalSize > 0 && renderHeaders()} - {filteredHelmAppList.map((app) => renderHelmAppLink(app))} - {showGuidedContentCards && ( -
- - -
- )} -
- ) - } - - function renderAllCheckModal() { - return ( -
- -
- ) - } - function askToSelectClusterId() { return (
@@ -550,17 +359,13 @@ const HelmAppList = ({ ) - return ( -
- -
- ) + return { + imgName: 'img-no-chart-in-clusters', + title: APP_LIST_EMPTY_STATE_MESSAGING.noHelmChartsFound, + subTitle: APP_LIST_EMPTY_STATE_MESSAGING.connectClusterInfoText, + isButtonAvailable: true, + renderButton: handleButton, + } } function renderHelmPermissionMessageStrip() { @@ -577,49 +382,47 @@ const HelmAppList = ({ ) } - function renderNoApplicationState() { + function renderAllCheckModal() { + return ( +
+ +
+ ) + } + + /** + * Determines the empty state configuration when no rows are present (before any filters are applied). + * 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 = () => { if (_isAnyFilterationAppliedExceptClusterAndNs() && !clusterIdsCsv) { - return askToClearFiltersWithSelectClusterTip() - } - if (_isOnlyAllClusterFilterationApplied()) { - return askToConnectAClusterForNoResult() - } - if (_isAnyFilterationApplied()) { - return + // Return null to render custom component + return null } if (!clusterIdsCsv) { - return askToSelectClusterId() + return null } - return renderAllCheckModal() + // Return null for allCheckModal - handled externally + return null } - function renderFullModeApplicationListContainer() { - if (!sseConnection && filteredListTotalSize === 0) { - return ( - <> - {serverMode === SERVER_MODE.FULL && renderHelmPermissionMessageStrip()} - {renderNoApplicationState()} - - ) + /** + * Determines the empty state configuration when filters are applied but no rows match. + */ + const getNoRowsForFilterConfig = () => { + if (isOnlyAllClusterFilterApplied) { + return askToConnectAClusterForNoResult() + } + return { + title: APP_LIST_EMPTY_STATE_MESSAGING.noHelmChartsFound, + subTitle: APP_LIST_EMPTY_STATE_MESSAGING.connectClusterInfoText, } - return renderApplicationList() } - function renderPagination(): JSX.Element { - return ( - filteredListTotalSize > DEFAULT_BASE_PAGE_SIZE && - !fetchingExternalApps && ( - - ) - ) - } if (dataStateType === AppListViewType.ERROR) { return (
@@ -627,28 +430,64 @@ const HelmAppList = ({
) } - return ( - <> - {dataStateType === AppListViewType.LOADING && ( + + // Handle edge cases where we don't show the table + if (!sseConnection && rows.length === 0 && dataStateType === AppListViewType.LIST) { + if (_isAnyFilterationAppliedExceptClusterAndNs() && !clusterIdsCsv) { + return ( <> - {renderHeaders()} -
- {appListLoadingArray.map((eachRow) => ( -
- {Object.keys(eachRow).map((eachKey) => ( -
- ))} -
- ))} -
+ {serverMode === SERVER_MODE.FULL && renderHelmPermissionMessageStrip()} + {askToClearFiltersWithSelectClusterTip()} - )} - {dataStateType === AppListViewType.LIST && ( -
- {renderFullModeApplicationListContainer()} - {renderPagination()} -
- )} + ) + } + if (!clusterIdsCsv) { + return ( + <> + {serverMode === SERVER_MODE.FULL && renderHelmPermissionMessageStrip()} + {askToSelectClusterId()} + + ) + } + if (!_isAnyFilterationApplied()) { + return ( + <> + {serverMode === SERVER_MODE.FULL && renderHelmPermissionMessageStrip()} + {renderAllCheckModal()} + + ) + } + } + + return ( + <> + {serverMode === SERVER_MODE.FULL && rows.length === 0 && dataStateType === AppListViewType.LIST && renderHelmPermissionMessageStrip()} + + id="table__helm-app-list" + columns={columns} + loading={dataStateType === AppListViewType.LOADING} + rows={rows} + filter={filter} + filtersVariant={FiltersTypeEnum.URL} + paginationVariant={PaginationEnum.PAGINATED} + emptyStateConfig={{ + noRowsConfig: getNoRowsConfig(), + noRowsForFilterConfig: getNoRowsForFilterConfig(), + }} + clearFilters={clearAllFilters} + onRowClick={onRowClick} + additionalFilterProps={{ + initialSortKey: AppListSortableKeys.APP_NAME, + }} + areFiltersApplied={isAnyFilterApplied} + ViewWrapper={HelmAppListViewWrapper} + additionalProps={{ + showGuidedContentCards, + externalHelmListFetchErrors, + clusterIdsCsv, + removeExternalAppFetchError: _removeExternalAppFetchError, + }} + /> ) } diff --git a/src/components/app/list-new/HelmAppListCellComponents.tsx b/src/components/app/list-new/HelmAppListCellComponents.tsx new file mode 100644 index 0000000000..da180829cb --- /dev/null +++ b/src/components/app/list-new/HelmAppListCellComponents.tsx @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react' +import { AppStatus, DATE_TIME_FORMATS, FiltersTypeEnum, handleUTCTime, TableCellProps } from '@devtron-labs/devtron-fe-common-lib' +import Tippy from '@tippyjs/react' +import moment from 'moment' +import { LazyImage } from '../../common' +import defaultChartImage from '../../../assets/icons/ic-default-chart.svg' +import { HelmAppListRowType } from './AppListType' + +const handleImageError = (event: React.SyntheticEvent) => { + const target = event.target as HTMLImageElement + target.onerror = null + target.src = defaultChartImage +} + +export const HelmAppNameCellComponent = ({ rowData }: TableCellProps) => { + const app = rowData.data.detail + return ( + <> +
+ +
+
+
{app.appName}
+
{app.chartName}
+
+ + ) +} + +export const HelmAppStatusCellComponent = ({ rowData }: TableCellProps) => { + const app = rowData.data.detail + return +} + +export const HelmAppEnvironmentCellComponent = ({ rowData }: TableCellProps) => { + const app = rowData.data.detail + return ( +

+ {app.environmentDetail.environmentName + ? app.environmentDetail.environmentName + : `${app.environmentDetail.clusterName}__${app.environmentDetail.namespace}`} +

+ ) +} + +export const HelmAppLastDeployedCellComponent = ({ rowData }: TableCellProps) => { + const app = rowData.data.detail + + if (!app.lastDeployedAt) { + return null + } + + return ( + +

{handleUTCTime(app.lastDeployedAt, true)}

+
+ ) +} diff --git a/src/components/app/list-new/HelmAppListViewWrapper.tsx b/src/components/app/list-new/HelmAppListViewWrapper.tsx new file mode 100644 index 0000000000..59ae01e0d7 --- /dev/null +++ b/src/components/app/list-new/HelmAppListViewWrapper.tsx @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024. Devtron Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentSizeType, DocLink, FiltersTypeEnum, TableViewWrapperProps, URLS as CommonURLS } from '@devtron-labs/devtron-fe-common-lib' +import { ReactComponent as InfoFillPurple } from '../../../assets/icons/ic-info-filled-purple.svg' +import { ReactComponent as ErrorExclamationIcon } from '../../../assets/icons/ic-error-exclamation.svg' +import { ReactComponent as CloseIcon } from '../../../assets/icons/ic-close.svg' +import { ReactComponent as ArrowRight } from '../../../assets/icons/ic-arrow-right.svg' +import HelmCluster from '../../../assets/img/guided-helm-cluster.png' +import DeployCICD from '../../../assets/img/guide-onboard.png' +import ContentCard from '../../common/ContentCard/ContentCard' +import { CardContentDirection, CardLinkIconPlacement } from '../../common/ContentCard/ContentCard.types' +import { HELM_GUIDED_CONTENT_CARDS_TEXTS } from '../../onboardingGuide/OnboardingGuide.constants' +import { SELECT_CLUSTER_FROM_FILTER_NOTE } from './Constants' +import { HelmAppListAdditionalProps, HelmAppListRowType } from './AppListType' +import { ModuleNameMap, URLS } from '../../../config' + +export const HelmAppListViewWrapper = ({ + children, + additionalProps, +}: TableViewWrapperProps) => { + const { showGuidedContentCards, externalHelmListFetchErrors, clusterIdsCsv, removeExternalAppFetchError } = additionalProps + + const renderFetchError = (externalHelmListFetchError: string, index: number) => ( +
+
+
+ + + + {externalHelmListFetchError} + removeExternalAppFetchError(index)} + /> +
+
+ ) + + return ( +
+ {!clusterIdsCsv && ( +
+
+
+ + + +
+ {SELECT_CLUSTER_FROM_FILTER_NOTE}  + +
+
+
+ )} + {externalHelmListFetchErrors.map((externalHelmListFetchError, index) => + renderFetchError(externalHelmListFetchError, index), + )} + {children} + {showGuidedContentCards && ( +
+ + +
+ )} +
+ ) +} diff --git a/src/components/app/list-new/list.utils.ts b/src/components/app/list-new/list.utils.ts index 172fbe6e70..ee934fb0b2 100644 --- a/src/components/app/list-new/list.utils.ts +++ b/src/components/app/list-new/list.utils.ts @@ -45,8 +45,15 @@ import { GenericAppListRowType, GetAppListFiltersParams, useFilterOptionsProps, + HelmAppListRowType, } from './AppListType' import { APP_LIST_HEADERS, ENVIRONMENT_HEADER_TIPPY_CONTENT, SELECT_CLUSTER_TIPPY } from './Constants' +import { + HelmAppNameCellComponent, + HelmAppStatusCellComponent, + HelmAppEnvironmentCellComponent, + HelmAppLastDeployedCellComponent, +} from './HelmAppListCellComponents' export const getAppTabNameFromAppType = (appType: InfrastructureManagementAppListType) => { switch (appType) { @@ -341,3 +348,62 @@ export const getGenericAppListColumns = (isFluxCDAppList: boolean) => }, }, ] as TableColumnType[] + +export const getHelmAppListColumns = (isArgoInstalled: boolean) => + [ + { + field: AppListSortableKeys.APP_NAME, + label: APP_LIST_HEADERS.ReleaseName, + isSortable: true, + size: { + fixed: 250, + }, + comparator: stringComparatorBySortOrder, + CellComponent: HelmAppNameCellComponent, + }, + ...(isArgoInstalled + ? [ + { + field: APP_LIST_HEADERS.AppStatus, + label: APP_LIST_HEADERS.AppStatus, + size: { + fixed: 164, + }, + CellComponent: HelmAppStatusCellComponent, + } as TableColumnType, + ] + : []), + { + field: APP_LIST_HEADERS.Environment, + label: APP_LIST_HEADERS.Environment, + size: { + fixed: 200, + }, + infoTooltipText: ENVIRONMENT_HEADER_TIPPY_CONTENT, + CellComponent: HelmAppEnvironmentCellComponent, + }, + { + field: APP_LIST_HEADERS.Cluster, + label: APP_LIST_HEADERS.Cluster, + size: { + fixed: 150, + }, + }, + { + field: APP_LIST_HEADERS.Namespace, + label: APP_LIST_HEADERS.Namespace, + size: { + fixed: 150, + }, + }, + { + field: AppListSortableKeys.LAST_DEPLOYED, + label: APP_LIST_HEADERS.LastDeployedAt, + isSortable: true, + size: { + fixed: 180, + }, + comparator: stringComparatorBySortOrder, + CellComponent: HelmAppLastDeployedCellComponent, + }, + ] as TableColumnType[] diff --git a/src/components/app/list-new/styles.scss b/src/components/app/list-new/styles.scss index ab1a58e1f3..eda3922e5a 100644 --- a/src/components/app/list-new/styles.scss +++ b/src/components/app/list-new/styles.scss @@ -18,3 +18,24 @@ padding: 0 0 0 20px; } } + +#table-wrapper-table__helm-app-list { + .generic-table__row { + padding-block: 10px; + padding-inline-end: 0px; + + & > div.generic-table__cell:nth-of-type(2) { + span { + color: var(--B500); + } + } + } + + .generic-table__row--expanded-row { + padding-block: 0px; + } + + .generic-table__header { + padding: 0 0 0 20px; + } +}