Skip to content

Commit a0e2669

Browse files
authored
Merge pull request #325 from devtron-labs/feat/collapsible-list-state
feat: state based collapsible list
2 parents 71998da + ae14f5c commit a0e2669

File tree

13 files changed

+210
-87
lines changed

13 files changed

+210
-87
lines changed

package-lock.json

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "0.5.6",
3+
"version": "0.5.8",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -84,7 +84,6 @@
8484
"react-draggable": "^4.4.5",
8585
"react-ga4": "^1.4.1",
8686
"react-mde": "^11.5.0",
87-
"react-router": "^5.3.0",
8887
"react-router-dom": "^5.3.0",
8988
"react-select": "5.8.0",
9089
"rxjs": "^7.8.1",

src/Common/Constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ export const URLS = {
6666
GLOBAL_CONFIG_SCOPED_VARIABLES: '/global-config/scoped-variables',
6767
GLOBAL_CONFIG_DEPLOYMENT_CHARTS_LIST: '/global-config/deployment-charts',
6868
NETWORK_STATUS_INTERFACE: '/network-status-interface',
69+
CONFIG_DRIFT: 'config-drift',
6970
}
7071

7172
export const ROUTES = {

src/Shared/Components/CICDHistory/AppStatusDetailsChart.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,34 @@
1616

1717
import { useMemo, useState } from 'react'
1818
import Tippy from '@tippyjs/react'
19+
import { useHistory } from 'react-router-dom'
20+
import { URLS } from '@Common/Constants'
1921
import { ReactComponent as InfoIcon } from '../../../Assets/Icon/ic-info-filled.svg'
2022
import { ReactComponent as Chat } from '../../../Assets/Icon/ic-chat-circle-dots.svg'
21-
import { AppStatusDetailsChartType, AggregatedNodes, STATUS_SORTING_ORDER } from './types'
23+
import { AppStatusDetailsChartType, AggregatedNodes, STATUS_SORTING_ORDER, NodeFilters } from './types'
2224
import { StatusFilterButtonComponent } from './StatusFilterButtonComponent'
23-
import { DEPLOYMENT_STATUS, APP_STATUS_HEADERS } from '../../constants'
25+
import { DEPLOYMENT_STATUS, APP_STATUS_HEADERS, ComponentSizeType } from '../../constants'
2426
import { IndexStore } from '../../Store'
2527
import { aggregateNodes } from '../../Helpers'
28+
import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
2629

27-
const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppStatusDetailsChartType) => {
30+
const AppStatusDetailsChart = ({
31+
filterRemoveHealth = false,
32+
showFooter,
33+
showConfigDriftInfo = false,
34+
onClose,
35+
}: AppStatusDetailsChartType) => {
36+
const history = useHistory()
2837
const _appDetails = IndexStore.getAppDetails()
2938
const [currentFilter, setCurrentFilter] = useState('')
3039

40+
const { appId, environmentId: envId } = _appDetails
41+
42+
const handleCompareDesiredManifest = () => {
43+
onClose()
44+
history.push(`${URLS.APP}/${appId}${URLS.DETAILS}/${envId}/${URLS.APP_DETAILS_K8}/${URLS.CONFIG_DRIFT}`)
45+
}
46+
3147
const nodes: AggregatedNodes = useMemo(
3248
() => aggregateNodes(_appDetails.resourceTree?.nodes || [], _appDetails.resourceTree?.podMetadata || []),
3349
[_appDetails],
@@ -100,6 +116,7 @@ const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppSt
100116
.filter(
101117
(nodeDetails) =>
102118
currentFilter === 'all' ||
119+
(currentFilter === NodeFilters.drifted && nodeDetails.hasDrift) ||
103120
nodeDetails.health.status?.toLowerCase() === currentFilter,
104121
)
105122
.map((nodeDetails) => (
@@ -123,7 +140,24 @@ const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppSt
123140
>
124141
{nodeDetails.status ? nodeDetails.status : nodeDetails.health.status}
125142
</div>
126-
<div>{getNodeMessage(nodeDetails.kind, nodeDetails.name)}</div>
143+
<div className="flexbox-col dc__gap-4">
144+
{showConfigDriftInfo && nodeDetails.hasDrift && (
145+
<div className="flexbox dc__gap-8 dc__align-items-center">
146+
<span className="fs-13 fw-4 lh-20 cy-7">Config drift detected</span>
147+
{onClose && appId && envId && (
148+
<Button
149+
dataTestId="show-config-drift"
150+
text="Compare with desired"
151+
variant={ButtonVariantType.text}
152+
style={ButtonStyleType.default}
153+
onClick={handleCompareDesiredManifest}
154+
size={ComponentSizeType.small}
155+
/>
156+
)}
157+
</div>
158+
)}
159+
<div>{getNodeMessage(nodeDetails.kind, nodeDetails.name)}</div>
160+
</div>
127161
</div>
128162
))
129163
) : (

src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
import { useEffect, useState } from 'react'
1919
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
2020
import { PopupMenu, StyledRadioGroup as RadioGroup } from '../../../Common'
21-
import { NodeStatus, StatusFilterButtonType } from './types'
21+
import { NodeFilters, NodeStatus, StatusFilterButtonType } from './types'
2222
import { IndexStore } from '../../Store'
23-
2423
import './StatusFilterButtonComponent.scss'
2524

2625
export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: StatusFilterButtonType) => {
@@ -32,10 +31,15 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
3231
let progressingNodeCount: number = 0
3332
let failedNodeCount: number = 0
3433
let missingNodeCount: number = 0
34+
let driftedNodeCount: number = 0
3535

3636
nodes?.forEach((_node) => {
3737
const _nodeHealth = _node.health?.status
3838

39+
if (_node.hasDrift) {
40+
driftedNodeCount += 1
41+
}
42+
3943
if (_nodeHealth?.toLowerCase() === NodeStatus.Healthy) {
4044
healthyNodeCount += 1
4145
} else if (_nodeHealth?.toLowerCase() === NodeStatus.Degraded) {
@@ -58,6 +62,11 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
5862
isSelected: NodeStatus.Progressing == selectedTab,
5963
},
6064
{ status: NodeStatus.Healthy, count: healthyNodeCount, isSelected: NodeStatus.Healthy == selectedTab },
65+
window._env_.FEATURE_CONFIG_DRIFT_ENABLE && {
66+
status: NodeFilters.drifted,
67+
count: driftedNodeCount,
68+
isSelected: selectedTab === NodeFilters.drifted,
69+
},
6170
]
6271
const validFilterOptions = filterOptions.filter(({ count }) => count > 0)
6372
const displayedInlineFilters = validFilterOptions.slice(
@@ -72,7 +81,8 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
7281
(selectedTab === NodeStatus.Healthy && healthyNodeCount === 0) ||
7382
(selectedTab === NodeStatus.Degraded && failedNodeCount === 0) ||
7483
(selectedTab === NodeStatus.Progressing && progressingNodeCount === 0) ||
75-
(selectedTab === NodeStatus.Missing && missingNodeCount === 0)
84+
(selectedTab === NodeStatus.Missing && missingNodeCount === 0) ||
85+
(selectedTab === NodeFilters.drifted && driftedNodeCount === 0)
7686
) {
7787
setSelectedTab('all')
7888
} else if (handleFilterClick) {

src/Shared/Components/CICDHistory/types.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ export interface DeploymentHistorySidebarType {
519519
export interface AppStatusDetailsChartType {
520520
filterRemoveHealth?: boolean
521521
showFooter: boolean
522+
showConfigDriftInfo?: boolean
523+
onClose?: () => void
522524
}
523525

524526
export interface StatusFilterButtonType {
@@ -535,6 +537,10 @@ export enum NodeStatus {
535537
Unknown = 'unknown',
536538
}
537539

540+
export enum NodeFilters {
541+
drifted = 'drifted',
542+
}
543+
538544
type NodesMap = {
539545
[key in NodeType]?: Map<string, any>
540546
}

src/Shared/Components/CollapsibleList/CollapsibleList.component.tsx

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ConditionalWrap } from '@Common/Helper'
55
import { ReactComponent as ICExpand } from '@Icons/ic-expand.svg'
66

77
import { Collapse } from '../Collapse'
8-
import { CollapsibleListProps } from './CollapsibleList.types'
8+
import { CollapsibleListItem, CollapsibleListProps, TabOptions } from './CollapsibleList.types'
99
import './CollapsibleList.scss'
1010

1111
const renderWithTippy = (tippyProps: TippyProps) => (children: React.ReactElement) => (
@@ -14,9 +14,80 @@ const renderWithTippy = (tippyProps: TippyProps) => (children: React.ReactElemen
1414
</Tippy>
1515
)
1616

17-
export const CollapsibleList = ({ config, onCollapseBtnClick }: CollapsibleListProps) => {
17+
export const CollapsibleList = <TabType extends TabOptions>({
18+
config,
19+
tabType,
20+
onCollapseBtnClick,
21+
}: CollapsibleListProps<TabType>) => {
1822
const { pathname } = useLocation()
1923

24+
const getTabContent = (item: CollapsibleListItem<TabOptions>) => {
25+
const { title, subtitle, strikeThrough, iconConfig } = item
26+
return (
27+
<>
28+
<div className="flexbox-col flex-grow-1 mw-none">
29+
<span
30+
className={`collapsible__item__title dc__truncate fs-13 lh-20 ${strikeThrough ? 'dc__strike-through' : ''}`}
31+
>
32+
{title}
33+
</span>
34+
{subtitle && <span className="dc__truncate fw-4 lh-1-5 cn-7">{subtitle}</span>}
35+
</div>
36+
{iconConfig && (
37+
<ConditionalWrap
38+
condition={!!iconConfig.tooltipProps}
39+
wrap={renderWithTippy(iconConfig.tooltipProps)}
40+
>
41+
<iconConfig.Icon
42+
{...iconConfig.props}
43+
className={`icon-dim-20 p-2 dc__no-shrink cursor ${iconConfig.props?.className || ''}`}
44+
/>
45+
</ConditionalWrap>
46+
)}
47+
</>
48+
)
49+
}
50+
51+
const getButtonTabItem = (item: CollapsibleListItem<'button'>) => {
52+
const { title, isActive, onClick } = item
53+
return (
54+
<button
55+
key={title}
56+
className={`collapsible__item flexbox dc__align-items-center dc__gap-8 dc__no-decor br-4 py-6 px-8 cursor ${isActive ? 'active' : ''} dc__unset-button-styles w-100`}
57+
onClick={(e) => {
58+
// Prevent navigation to the same page
59+
if (isActive) {
60+
e.preventDefault()
61+
}
62+
onClick?.(e)
63+
}}
64+
type="button"
65+
>
66+
{getTabContent(item)}
67+
</button>
68+
)
69+
}
70+
71+
const getNavLinkTabItem = (item: CollapsibleListItem<'navLink'>) => {
72+
const { title, href, onClick } = item
73+
return (
74+
<NavLink
75+
key={title}
76+
to={href}
77+
className="collapsible__item flexbox dc__align-items-center dc__gap-8 dc__no-decor br-4 py-6 px-8 cursor"
78+
onClick={(e) => {
79+
// Prevent navigation to the same page
80+
if (href === pathname) {
81+
e.preventDefault()
82+
}
83+
onClick?.(e)
84+
}}
85+
>
86+
{getTabContent(item)}
87+
</NavLink>
88+
)
89+
}
90+
2091
return (
2192
<div className="mw-none bcn-0">
2293
{config.map(({ id, header, headerIconConfig, items, noItemsText, isExpanded }) => (
@@ -60,42 +131,11 @@ export const CollapsibleList = ({ config, onCollapseBtnClick }: CollapsibleListP
60131
</span>
61132
</div>
62133
) : (
63-
items.map(({ title, strikeThrough, href, iconConfig, subtitle, onClick }) => (
64-
<NavLink
65-
key={title}
66-
to={href}
67-
className="collapsible__item flexbox dc__align-items-center dc__gap-8 dc__no-decor br-4 py-6 px-8 cursor"
68-
onClick={(e) => {
69-
// Prevent navigation to the same page
70-
if (href === pathname) {
71-
e.preventDefault()
72-
}
73-
onClick?.(e)
74-
}}
75-
>
76-
<div className="flexbox-col flex-grow-1 mw-none">
77-
<span
78-
className={`collapsible__item__title dc__truncate fs-13 lh-20 ${strikeThrough ? 'dc__strike-through' : ''}`}
79-
>
80-
{title}
81-
</span>
82-
{subtitle && (
83-
<span className="dc__truncate fw-4 lh-1-5 cn-7">{subtitle}</span>
84-
)}
85-
</div>
86-
{iconConfig && (
87-
<ConditionalWrap
88-
condition={!!iconConfig.tooltipProps}
89-
wrap={renderWithTippy(iconConfig.tooltipProps)}
90-
>
91-
<iconConfig.Icon
92-
{...iconConfig.props}
93-
className={`icon-dim-20 p-2 dc__no-shrink cursor ${iconConfig.props?.className || ''}`}
94-
/>
95-
</ConditionalWrap>
96-
)}
97-
</NavLink>
98-
))
134+
items.map((item) =>
135+
tabType === 'button'
136+
? getButtonTabItem(item as CollapsibleListItem<'button'>)
137+
: getNavLinkTabItem(item as CollapsibleListItem<'navLink'>),
138+
)
99139
)}
100140
</div>
101141
</Collapse>

0 commit comments

Comments
 (0)