Skip to content

feat: table in RB #2707

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

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5170f92
feat: add table story
Elessar1802 Mar 28, 2025
9f2f6d3
chore: update common-lib version
Elessar1802 Mar 28, 2025
b13796b
fix: add visible-modal div to preview.tsx for storybook
Elessar1802 Mar 28, 2025
0ee68c6
revert: remove form__checkbox-parent class
Elessar1802 Mar 31, 2025
13ee562
Merge branch 'develop' of github.com:devtron-labs/dashboard into feat…
Elessar1802 Mar 31, 2025
8612e04
fix: add correct id in table component story
Elessar1802 Mar 31, 2025
5b06a24
chore: update common-lib version
Elessar1802 Apr 1, 2025
006a739
Merge branch 'develop' of github.com:devtron-labs/dashboard into feat…
Elessar1802 Apr 2, 2025
a1a5234
revert: yarn.lock to develop & update common-lib version
Elessar1802 Apr 2, 2025
c49028a
fix(storybook): removeEventListener in useEffect cleanup for table story
Elessar1802 Apr 2, 2025
e4e0213
feat: route based RB
Elessar1802 May 6, 2025
6fa5a02
Merge branch 'develop' of github.com:devtron-labs/dashboard into fix/…
Elessar1802 May 6, 2025
a5ccc34
fix: make RB route based
Elessar1802 May 7, 2025
973dd6f
feat: use table in RB
Elessar1802 May 13, 2025
a354eb8
fix: redirection urls & cluster upgrade table
Elessar1802 May 15, 2025
8334364
fix: review comments
Elessar1802 May 19, 2025
00ad865
Merge branch 'develop' of github.com:devtron-labs/dashboard into feat…
Elessar1802 May 19, 2025
614c3b1
Merge branch 'develop' of github.com:devtron-labs/dashboard into feat…
Elessar1802 May 19, 2025
96138a8
Merge branch 'feat/route-based-rb' into feat/rb-table
Elessar1802 May 19, 2025
bd50e04
fix: review comments
Elessar1802 May 19, 2025
a0c8837
fix: RB table bulk actions props
Elessar1802 May 20, 2025
29af153
fix: forward ref in NodeActionsMenu
Elessar1802 May 21, 2025
4e0e005
fix: only override column type for event list
Elessar1802 May 21, 2025
c57cade
Merge branch 'feat/rb-table' of github.com:devtron-labs/dashboard int…
Elessar1802 May 28, 2025
5a58b24
Merge pull request #2600 from devtron-labs/feat/table-storybook
Elessar1802 May 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const preview: Preview = {
<div id="animated-dialog-backdrop" />
<div id="visible-modal" />

<div id="visible-modal" />
Copy link
Preview

Copilot AI May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff introduces an extra

with the same id 'visible-modal'. Duplicate DOM element IDs can lead to potential conflicts; consider removing or renaming the duplicate element.

Suggested change
<div id="visible-modal" />

Copilot uses AI. Check for mistakes.


<BaseConfirmationModal />
</ConfirmationModalProvider>
</ThemeProvider>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"test:ci": "jest --watchAll=false --ci --json --coverage --testLocationInResults --outputFile=report.json",
"jest": "jest",
"lint-staged": "lint-staged",
"storybook": "IS_STORYBOOK=true storybook dev -p 6006",
"storybook": "IS_STORYBOOK=true GENERATE_SOURCEMAP=true storybook dev -p 6006",
"build-storybook": "IS_STORYBOOK=true storybook build",
"postinstall": "patch-package"
},
Expand Down
13 changes: 5 additions & 8 deletions src/components/ClusterNodes/ClusterList/ClusterListRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Link } from 'react-router-dom'
import { generatePath, Link } from 'react-router-dom'

import {
ALL_NAMESPACE_OPTION,
BulkSelectionIdentifiersType,
Button,
ButtonComponentType,
Expand All @@ -19,10 +18,8 @@ import {

import { ReactComponent as Error } from '@Icons/ic-error-exclamation.svg'
import { importComponentFromFELibrary } from '@Components/common'
import { K8S_EMPTY_GROUP } from '@Components/ResourceBrowser/Constants'
import { RESOURCE_BROWSER_ROUTES } from '@Components/ResourceBrowser/Constants'
import { getClusterChangeRedirectionUrl } from '@Components/ResourceBrowser/Utils'
import { AppDetailsTabs } from '@Components/v2/appDetails/appDetails.store'
import { URLS } from '@Config/routes'

import { ClusterMapInitialStatus } from '../ClusterMapInitialStatus'
import { CLUSTER_PROD_TYPE } from '../constants'
Expand Down Expand Up @@ -65,8 +62,6 @@ const ClusterListRow = ({

const isIdentifierSelected = !!bulkSelectionState[clusterData.name]

// TODO: @Elessar1802 will be replacing all terminal url with new utils

const isClusterInCreationPhase = !!clusterData.installationId && !clusterData.id

const clusterLinkURL = getClusterChangeRedirectionUrl(
Expand Down Expand Up @@ -113,7 +108,9 @@ const ClusterListRow = ({
variant={ButtonVariantType.borderLess}
component={ButtonComponentType.link}
linkProps={{
to: `${URLS.RESOURCE_BROWSER}/${clusterData.id}/${ALL_NAMESPACE_OPTION.value}/${AppDetailsTabs.terminal}/${K8S_EMPTY_GROUP}`,
to: generatePath(RESOURCE_BROWSER_ROUTES.TERMINAL, {
clusterId: clusterData.id,
}),
}}
/>
)}
Expand Down
25 changes: 11 additions & 14 deletions src/components/ClusterNodes/ClusterOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import React, { useEffect, useRef, useState } from 'react'
import { generatePath, useHistory, useParams, useRouteMatch } from 'react-router-dom'
import { generatePath, useHistory, useParams } from 'react-router-dom'

import {
EditableTextArea,
Expand All @@ -34,7 +34,7 @@ import {
} from '@devtron-labs/devtron-fe-common-lib'

import { getUpgradeCompatibilityTippyConfig } from '@Components/ResourceBrowser/ResourceList/utils'
import { getURLBasedOnSidebarGVK } from '@Components/ResourceBrowser/Utils'
import { ClusterDetailBaseParams } from '@Components/ResourceBrowser/Types'
import { getAvailableCharts } from '@Services/service'

import { ReactComponent as Error } from '../../assets/icons/ic-error-exclamation.svg'
Expand All @@ -43,7 +43,9 @@ import { MAX_LENGTH_350 } from '../../config/constantMessaging'
import { importComponentFromFELibrary } from '../common'
import GenericDescription from '../common/Description/GenericDescription'
import {
DUMMY_RESOURCE_GVK_VERSION,
K8S_EMPTY_GROUP,
RESOURCE_BROWSER_ROUTES,
SIDEBAR_KEYS,
TARGET_K8S_VERSION_SEARCH_KEY,
UPGRADE_CLUSTER_CONSTANTS,
Expand Down Expand Up @@ -115,13 +117,9 @@ const LoadingMetricCard = () => (
)

function ClusterOverview({ selectedCluster, addTab }: ClusterOverviewProps) {
const { clusterId, namespace } = useParams<{
clusterId: string
namespace: string
}>()
const { clusterId } = useParams<ClusterDetailBaseParams>()

const history = useHistory()
const { path } = useRouteMatch()
const [clusterConfig, setClusterConfig] = useState<InstallationClusterConfigType | null>(null)

const requestAbortControllerRef = useRef(new AbortController())
Expand Down Expand Up @@ -212,11 +210,11 @@ function ClusterOverview({ selectedCluster, addTab }: ClusterOverviewProps) {

const setCustomFilter = (errorType: ERROR_TYPE, filterText: string): void => {
const queryParam = errorType === ERROR_TYPE.VERSION_ERROR ? 'k8sversion' : 'name'
const newUrl = `${generatePath(path, {
const newUrl = `${generatePath(RESOURCE_BROWSER_ROUTES.K8S_RESOURCE_LIST, {
clusterId,
namespace,
nodeType: SIDEBAR_KEYS.nodeGVK.Kind.toLowerCase(),
kind: 'node',
group: K8S_EMPTY_GROUP,
version: DUMMY_RESOURCE_GVK_VERSION,
})}?${queryParam}=${encodeURIComponent(filterText)}`
history.push(newUrl)
}
Expand Down Expand Up @@ -343,10 +341,9 @@ function ClusterOverview({ selectedCluster, addTab }: ClusterOverviewProps) {
const handleOpenScanClusterTab = (selectedVersion: string) => {
const upgradeClusterLowerCaseKind = SIDEBAR_KEYS.upgradeClusterGVK.Kind.toLowerCase()

const URL = getUrlWithSearchParams(
getURLBasedOnSidebarGVK(SIDEBAR_KEYS.upgradeClusterGVK.Kind, clusterId, namespace),
{ [TARGET_K8S_VERSION_SEARCH_KEY]: selectedVersion },
)
const URL = getUrlWithSearchParams(generatePath(RESOURCE_BROWSER_ROUTES.CLUSTER_UPGRADE, { clusterId }), {
[TARGET_K8S_VERSION_SEARCH_KEY]: selectedVersion,
})

addTab({
idPrefix: UPGRADE_CLUSTER_CONSTANTS.ID_PREFIX,
Expand Down
15 changes: 6 additions & 9 deletions src/components/ClusterNodes/ClusterTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ import {
TabProps,
} from '@devtron-labs/devtron-fe-common-lib'

import { K8sResourceListURLParams } from '@Components/ResourceBrowser/ResourceList/types'

import { BUSYBOX_LINK, DEFAULT_CONTAINER_NAME, NETSHOOT_LINK, shellTypes } from '../../config/constants'
import { getClusterTerminalParamsData } from '../cluster/cluster.util'
import { clusterImageDescription, convertToOptionsList } from '../common'
import { URLParams } from '../ResourceBrowser/Types'
import { AppDetailsTabs } from '../v2/appDetails/appDetails.store'
import {
EditModeType,
Expand Down Expand Up @@ -83,7 +84,7 @@ const ClusterTerminal = ({
taints,
updateTerminalTabUrl,
}: ClusterTerminalType) => {
const { nodeType } = useParams<URLParams>()
const { kind } = useParams<K8sResourceListURLParams>()
const { replace } = useHistory()
const location = useLocation()
const queryParams = new URLSearchParams(location.search)
Expand Down Expand Up @@ -154,11 +155,7 @@ const ClusterTerminal = ({
}

useEffect(() => {
if (
nodeType !== AppDetailsTabs.terminal ||
queryParamsData.selectedNode.value === selectedNodeName.value ||
!update
) {
if (kind !== 'terminal' || queryParamsData.selectedNode.value === selectedNodeName.value || !update) {
return
}
/* NOTE: update selectedNodeName */
Expand All @@ -184,7 +181,7 @@ const ClusterTerminal = ({
queryParams.set('shell', selectedTerminalType.value)
queryParams.set('node', selectedNodeName.value)
updateTerminalTabUrl(queryParams.toString())
if (nodeType === AppDetailsTabs.terminal) {
if (kind === AppDetailsTabs.terminal) {
replace({ search: queryParams.toString() })
}
}, [selectedNodeName.value, selectedNamespace.value, selectedImage.value, selectedTerminalType.value])
Expand Down Expand Up @@ -1018,7 +1015,7 @@ const ClusterTerminal = ({
renderConnectionStrip: renderStripMessage(),
setSocketConnection,
socketConnection,
isTerminalTab: selectedTabIndex === 0 && nodeType === AppDetailsTabs.terminal,
isTerminalTab: selectedTabIndex === 0 && kind === 'terminal',
sessionId,
registerLinkMatcher: renderRegisterLinkMatcher,
},
Expand Down
20 changes: 10 additions & 10 deletions src/components/ClusterNodes/NodeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import './clusterNodes.scss'
import ResourceBrowserActionMenu from '../ResourceBrowser/ResourceList/ResourceBrowserActionMenu'

const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }: ClusterListType) => {
const { clusterId, node } = useParams<{ clusterId: string; nodeType: string; node: string }>()
const { clusterId, name } = useParams<{ clusterId: string; nodeType: string; name: string }>()
const [loader, setLoader] = useState(true)
const [apiInProgress, setApiInProgress] = useState(false)
const [isReviewState, setIsReviewStates] = useState(false)
Expand Down Expand Up @@ -112,7 +112,7 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:
const getData = (_patchdata: jsonpatch.Operation[]) => {
setLoader(true)
setErrorResponseCode(null)
getNodeCapacity(clusterId, node)
getNodeCapacity(clusterId, name)
.then((response: NodeDetailResponse) => {
if (response.result) {
setSortedPodList(response.result.pods?.sort((a, b) => a['name'].localeCompare(b['name'])))
Expand Down Expand Up @@ -150,7 +150,7 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:

useEffect(() => {
getData(patchData)
}, [node])
}, [name])

useEffect(() => {
if (queryParams.has('tab')) {
Expand Down Expand Up @@ -181,7 +181,7 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:

const changeNodeTab = (e): void => {
const _tabIndex = Number(e.currentTarget.dataset.tabIndex)
if (node !== AUTO_SELECT.value) {
if (name !== AUTO_SELECT.value) {
let _searchParam = '?tab='
if (_tabIndex === 0) {
_searchParam += NODE_DETAILS_TABS.summary.toLowerCase()
Expand Down Expand Up @@ -863,13 +863,13 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:
const parsedManifest = YAML.parse(modifiedManifest)
const requestData: UpdateNodeRequestBody = {
clusterId: +clusterId,
name: node,
name: name,
manifestPatch: JSON.stringify(parsedManifest),
version: nodeDetail.version,
kind: nodeDetail.kind,
}
setApiInProgress(true)
updateNodeManifest(clusterId, node, requestData)
updateNodeManifest(clusterId, name, requestData)
.then((response: NodeDetailResponse) => {
setApiInProgress(false)
if (response.result) {
Expand Down Expand Up @@ -1059,7 +1059,7 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:
{renderTabs()}
{showCordonNodeDialog && (
<CordonNodeModal
name={node}
name={name}
version={nodeDetail.version}
kind={nodeDetail.kind}
unschedulable={nodeDetail.unschedulable}
Expand All @@ -1068,15 +1068,15 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:
)}
{showDrainNodeDialog && (
<DrainNodeModal
name={node}
name={name}
version={nodeDetail.version}
kind={nodeDetail.kind}
closePopup={hideDrainNodeModal}
/>
)}
{showDeleteNodeDialog && (
<DeleteNodeModal
name={node}
name={name}
version={nodeDetail.version}
kind={nodeDetail.kind}
closePopup={hideDeleteNodeModal}
Expand All @@ -1085,7 +1085,7 @@ const NodeDetails = ({ addTab, lowercaseKindToResourceGroupMap, updateTabUrl }:
)}
{showEditTaints && (
<EditTaintsModal
name={node}
name={name}
version={nodeDetail.version}
kind={nodeDetail.kind}
taints={nodeDetail.taints}
Expand Down
16 changes: 13 additions & 3 deletions src/components/ResourceBrowser/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import { NO_MATCHING_RESULT, Nodes } from '@devtron-labs/devtron-fe-common-lib'
import { NO_MATCHING_RESULT, Nodes, URLS } from '@devtron-labs/devtron-fe-common-lib'

import ICArrowUpCircle from '@Icons/ic-arrow-up-circle.svg'

Expand Down Expand Up @@ -351,8 +351,6 @@ export const NODE_SEARCH_KEYS_TO_OBJECT_KEYS: Record<

export const LOCAL_STORAGE_EXISTS = !!(Storage && localStorage)

export const LOCAL_STORAGE_KEY_FOR_APPLIED_COLUMNS = 'appliedColumns'

export const NODE_K8S_VERSION_FILTER_KEY = 'k8sVersion'

export const MONITORING_DASHBOARD_TAB_ID = 'monitoring_dashboard'
Expand Down Expand Up @@ -392,3 +390,15 @@ export const AI_BUTTON_CONFIG_MAP: Record<string, ShowAIButtonConfig> = Object.f
excludeValues: new Set(['Approved,Issued']),
},
})

export const RESOURCE_BROWSER_ROUTES = {
OVERVIEW: `${URLS.RESOURCE_BROWSER}/:clusterId/overview`,
MONITORING_DASHBOARD: `${URLS.RESOURCE_BROWSER}/:clusterId/monitoring-dashboard`,
TERMINAL: `${URLS.RESOURCE_BROWSER}/:clusterId/terminal`,
CLUSTER_UPGRADE: `${URLS.RESOURCE_BROWSER}/:clusterId/cluster-upgrade`,
NODE_DETAIL: `${URLS.RESOURCE_BROWSER}/:clusterId/node/:name`,
K8S_RESOURCE_DETAIL: `${URLS.RESOURCE_BROWSER}/:clusterId/:namespace/:kind/:group/:version/:name`,
K8S_RESOURCE_LIST: `${URLS.RESOURCE_BROWSER}/:clusterId/:kind/:group/:version`,
} as const

export const DUMMY_RESOURCE_GVK_VERSION = 'v1'
53 changes: 36 additions & 17 deletions src/components/ResourceBrowser/ResourceBrowser.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import {

import { Routes } from '../../config'
import { SIDEBAR_KEYS } from './Constants'
import { GetResourceDataType, NodeRowDetail, URLParams } from './Types'
import { parseNodeList } from './Utils'
import { ClusterDetailBaseParams, GetResourceDataType, NodeRowDetail } from './Types'
import { parseNodeList, removeDefaultForStorageClass } from './Utils'

export const namespaceListByClusterId = async (clusterId: string) => {
const response = await get<string[]>(`${Routes.CLUSTER_NAMESPACE}/${clusterId}`)
Expand All @@ -60,9 +60,14 @@ export const getNodeList = (
clusterId: string,
abortControllerRef: RefObject<AbortController>,
): Promise<ResponseType<NodeRowDetail[]>> =>
get(getUrlWithSearchParams<keyof URLParams>(Routes.NODE_LIST, { clusterId: Number(clusterId) }), {
abortControllerRef,
})
get(
getUrlWithSearchParams<keyof ClusterDetailBaseParams>(Routes.NODE_LIST, {
clusterId: Number(clusterId),
}),
{
abortControllerRef,
},
)

export const getResourceData = async ({
selectedResource,
Expand All @@ -71,18 +76,20 @@ export const getResourceData = async ({
filters,
abortControllerRef,
}: GetResourceDataType) => {
const idPrefix = `${clusterId}${JSON.stringify(selectedResource)}${JSON.stringify(filters)}${selectedNamespace}`

try {
if (selectedResource.gvk.Kind === SIDEBAR_KEYS.nodeGVK.Kind) {
const response = await getNodeList(clusterId, abortControllerRef)

return parseNodeList(response)
return parseNodeList(response, idPrefix)
}

const isNamespaceList = selectedResource.gvk.Kind.toLowerCase() === Nodes.Namespace.toLowerCase()

const [k8sResponse, namespaceListResponse] = await Promise.allSettled([
getK8sResourceList(
getK8sResourceListPayload(clusterId, selectedNamespace.value.toLowerCase(), selectedResource, filters),
getK8sResourceListPayload(clusterId, selectedNamespace.toLowerCase(), selectedResource, filters),
abortControllerRef.current.signal,
),
isNamespaceList ? getNamespaceListMin(clusterId, abortControllerRef) : null,
Expand All @@ -107,19 +114,31 @@ export const getResourceData = async ({
)

return {
...response,
result: {
...response.result,
headers: [...response.result.headers, 'environment'],
data: response.result.data.map((data) => ({
...data,
environment: namespaceToEnvironmentMap[data.name as string],
})),
},
headers: [...response.result.headers, 'environment'],
data: response.result.data.map((data, index) => ({
...data,
environment: namespaceToEnvironmentMap[data.name as string],
id: `${idPrefix}${index}`,
})),
}
}

return response
if (!response) {
return null
}

const data =
selectedResource.gvk.Kind === Nodes.StorageClass
? removeDefaultForStorageClass(response.result.data)
: response.result.data

return {
...response.result,
data: data.map((entry, index) => ({
...entry,
id: `${idPrefix}${index}`,
})),
}
} catch (err) {
if (!getIsRequestAborted(err)) {
showError(err)
Expand Down
Loading