diff --git a/package-lock.json b/package-lock.json
index a8d998cee..5b2219979 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "0.5.6",
+ "version": "0.5.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "0.5.6",
+ "version": "0.5.8",
"license": "ISC",
"dependencies": {
"@types/react-dates": "^21.8.6",
@@ -70,7 +70,6 @@
"react-draggable": "^4.4.5",
"react-ga4": "^1.4.1",
"react-mde": "^11.5.0",
- "react-router": "^5.3.0",
"react-router-dom": "^5.3.0",
"react-select": "5.8.0",
"rxjs": "^7.8.1",
diff --git a/package.json b/package.json
index 5faedf0b3..71f17bf42 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@devtron-labs/devtron-fe-common-lib",
- "version": "0.5.6",
+ "version": "0.5.8",
"description": "Supporting common component library",
"type": "module",
"main": "dist/index.js",
@@ -84,7 +84,6 @@
"react-draggable": "^4.4.5",
"react-ga4": "^1.4.1",
"react-mde": "^11.5.0",
- "react-router": "^5.3.0",
"react-router-dom": "^5.3.0",
"react-select": "5.8.0",
"rxjs": "^7.8.1",
diff --git a/src/Common/Constants.ts b/src/Common/Constants.ts
index f15e80ef6..237afd4da 100644
--- a/src/Common/Constants.ts
+++ b/src/Common/Constants.ts
@@ -66,6 +66,7 @@ export const URLS = {
GLOBAL_CONFIG_SCOPED_VARIABLES: '/global-config/scoped-variables',
GLOBAL_CONFIG_DEPLOYMENT_CHARTS_LIST: '/global-config/deployment-charts',
NETWORK_STATUS_INTERFACE: '/network-status-interface',
+ CONFIG_DRIFT: 'config-drift',
}
export const ROUTES = {
diff --git a/src/Shared/Components/CICDHistory/AppStatusDetailsChart.tsx b/src/Shared/Components/CICDHistory/AppStatusDetailsChart.tsx
index b704befad..c8f797bef 100644
--- a/src/Shared/Components/CICDHistory/AppStatusDetailsChart.tsx
+++ b/src/Shared/Components/CICDHistory/AppStatusDetailsChart.tsx
@@ -16,18 +16,34 @@
import { useMemo, useState } from 'react'
import Tippy from '@tippyjs/react'
+import { useHistory } from 'react-router-dom'
+import { URLS } from '@Common/Constants'
import { ReactComponent as InfoIcon } from '../../../Assets/Icon/ic-info-filled.svg'
import { ReactComponent as Chat } from '../../../Assets/Icon/ic-chat-circle-dots.svg'
-import { AppStatusDetailsChartType, AggregatedNodes, STATUS_SORTING_ORDER } from './types'
+import { AppStatusDetailsChartType, AggregatedNodes, STATUS_SORTING_ORDER, NodeFilters } from './types'
import { StatusFilterButtonComponent } from './StatusFilterButtonComponent'
-import { DEPLOYMENT_STATUS, APP_STATUS_HEADERS } from '../../constants'
+import { DEPLOYMENT_STATUS, APP_STATUS_HEADERS, ComponentSizeType } from '../../constants'
import { IndexStore } from '../../Store'
import { aggregateNodes } from '../../Helpers'
+import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
-const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppStatusDetailsChartType) => {
+const AppStatusDetailsChart = ({
+ filterRemoveHealth = false,
+ showFooter,
+ showConfigDriftInfo = false,
+ onClose,
+}: AppStatusDetailsChartType) => {
+ const history = useHistory()
const _appDetails = IndexStore.getAppDetails()
const [currentFilter, setCurrentFilter] = useState('')
+ const { appId, environmentId: envId } = _appDetails
+
+ const handleCompareDesiredManifest = () => {
+ onClose()
+ history.push(`${URLS.APP}/${appId}${URLS.DETAILS}/${envId}/${URLS.APP_DETAILS_K8}/${URLS.CONFIG_DRIFT}`)
+ }
+
const nodes: AggregatedNodes = useMemo(
() => aggregateNodes(_appDetails.resourceTree?.nodes || [], _appDetails.resourceTree?.podMetadata || []),
[_appDetails],
@@ -100,6 +116,7 @@ const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppSt
.filter(
(nodeDetails) =>
currentFilter === 'all' ||
+ (currentFilter === NodeFilters.drifted && nodeDetails.hasDrift) ||
nodeDetails.health.status?.toLowerCase() === currentFilter,
)
.map((nodeDetails) => (
@@ -123,7 +140,24 @@ const AppStatusDetailsChart = ({ filterRemoveHealth = false, showFooter }: AppSt
>
{nodeDetails.status ? nodeDetails.status : nodeDetails.health.status}
-
{getNodeMessage(nodeDetails.kind, nodeDetails.name)}
+
+ {showConfigDriftInfo && nodeDetails.hasDrift && (
+
+ Config drift detected
+ {onClose && appId && envId && (
+
+ )}
+
+ )}
+
{getNodeMessage(nodeDetails.kind, nodeDetails.name)}
+
))
) : (
diff --git a/src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx b/src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx
index 0ece827ea..06f2d77a0 100644
--- a/src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx
+++ b/src/Shared/Components/CICDHistory/StatusFilterButtonComponent.tsx
@@ -18,9 +18,8 @@
import { useEffect, useState } from 'react'
import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
import { PopupMenu, StyledRadioGroup as RadioGroup } from '../../../Common'
-import { NodeStatus, StatusFilterButtonType } from './types'
+import { NodeFilters, NodeStatus, StatusFilterButtonType } from './types'
import { IndexStore } from '../../Store'
-
import './StatusFilterButtonComponent.scss'
export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: StatusFilterButtonType) => {
@@ -32,10 +31,15 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
let progressingNodeCount: number = 0
let failedNodeCount: number = 0
let missingNodeCount: number = 0
+ let driftedNodeCount: number = 0
nodes?.forEach((_node) => {
const _nodeHealth = _node.health?.status
+ if (_node.hasDrift) {
+ driftedNodeCount += 1
+ }
+
if (_nodeHealth?.toLowerCase() === NodeStatus.Healthy) {
healthyNodeCount += 1
} else if (_nodeHealth?.toLowerCase() === NodeStatus.Degraded) {
@@ -58,6 +62,11 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
isSelected: NodeStatus.Progressing == selectedTab,
},
{ status: NodeStatus.Healthy, count: healthyNodeCount, isSelected: NodeStatus.Healthy == selectedTab },
+ window._env_.FEATURE_CONFIG_DRIFT_ENABLE && {
+ status: NodeFilters.drifted,
+ count: driftedNodeCount,
+ isSelected: selectedTab === NodeFilters.drifted,
+ },
]
const validFilterOptions = filterOptions.filter(({ count }) => count > 0)
const displayedInlineFilters = validFilterOptions.slice(
@@ -72,7 +81,8 @@ export const StatusFilterButtonComponent = ({ nodes, handleFilterClick }: Status
(selectedTab === NodeStatus.Healthy && healthyNodeCount === 0) ||
(selectedTab === NodeStatus.Degraded && failedNodeCount === 0) ||
(selectedTab === NodeStatus.Progressing && progressingNodeCount === 0) ||
- (selectedTab === NodeStatus.Missing && missingNodeCount === 0)
+ (selectedTab === NodeStatus.Missing && missingNodeCount === 0) ||
+ (selectedTab === NodeFilters.drifted && driftedNodeCount === 0)
) {
setSelectedTab('all')
} else if (handleFilterClick) {
diff --git a/src/Shared/Components/CICDHistory/types.tsx b/src/Shared/Components/CICDHistory/types.tsx
index 558b5806a..ec1d2f6b9 100644
--- a/src/Shared/Components/CICDHistory/types.tsx
+++ b/src/Shared/Components/CICDHistory/types.tsx
@@ -519,6 +519,8 @@ export interface DeploymentHistorySidebarType {
export interface AppStatusDetailsChartType {
filterRemoveHealth?: boolean
showFooter: boolean
+ showConfigDriftInfo?: boolean
+ onClose?: () => void
}
export interface StatusFilterButtonType {
@@ -535,6 +537,10 @@ export enum NodeStatus {
Unknown = 'unknown',
}
+export enum NodeFilters {
+ drifted = 'drifted',
+}
+
type NodesMap = {
[key in NodeType]?: Map
}
diff --git a/src/Shared/Components/CollapsibleList/CollapsibleList.component.tsx b/src/Shared/Components/CollapsibleList/CollapsibleList.component.tsx
index 3928907f1..960ec0b36 100644
--- a/src/Shared/Components/CollapsibleList/CollapsibleList.component.tsx
+++ b/src/Shared/Components/CollapsibleList/CollapsibleList.component.tsx
@@ -5,7 +5,7 @@ import { ConditionalWrap } from '@Common/Helper'
import { ReactComponent as ICExpand } from '@Icons/ic-expand.svg'
import { Collapse } from '../Collapse'
-import { CollapsibleListProps } from './CollapsibleList.types'
+import { CollapsibleListItem, CollapsibleListProps, TabOptions } from './CollapsibleList.types'
import './CollapsibleList.scss'
const renderWithTippy = (tippyProps: TippyProps) => (children: React.ReactElement) => (
@@ -14,9 +14,80 @@ const renderWithTippy = (tippyProps: TippyProps) => (children: React.ReactElemen
)
-export const CollapsibleList = ({ config, onCollapseBtnClick }: CollapsibleListProps) => {
+export const CollapsibleList = ({
+ config,
+ tabType,
+ onCollapseBtnClick,
+}: CollapsibleListProps) => {
const { pathname } = useLocation()
+ const getTabContent = (item: CollapsibleListItem) => {
+ const { title, subtitle, strikeThrough, iconConfig } = item
+ return (
+ <>
+
+
+ {title}
+
+ {subtitle && {subtitle}}
+
+ {iconConfig && (
+
+
+
+ )}
+ >
+ )
+ }
+
+ const getButtonTabItem = (item: CollapsibleListItem<'button'>) => {
+ const { title, isActive, onClick } = item
+ return (
+
+ )
+ }
+
+ const getNavLinkTabItem = (item: CollapsibleListItem<'navLink'>) => {
+ const { title, href, onClick } = item
+ return (
+ {
+ // Prevent navigation to the same page
+ if (href === pathname) {
+ e.preventDefault()
+ }
+ onClick?.(e)
+ }}
+ >
+ {getTabContent(item)}
+
+ )
+ }
+
return (
{config.map(({ id, header, headerIconConfig, items, noItemsText, isExpanded }) => (
@@ -60,42 +131,11 @@ export const CollapsibleList = ({ config, onCollapseBtnClick }: CollapsibleListP
) : (
- items.map(({ title, strikeThrough, href, iconConfig, subtitle, onClick }) => (
- {
- // Prevent navigation to the same page
- if (href === pathname) {
- e.preventDefault()
- }
- onClick?.(e)
- }}
- >
-
-
- {title}
-
- {subtitle && (
- {subtitle}
- )}
-
- {iconConfig && (
-
-
-
- )}
-
- ))
+ items.map((item) =>
+ tabType === 'button'
+ ? getButtonTabItem(item as CollapsibleListItem<'button'>)
+ : getNavLinkTabItem(item as CollapsibleListItem<'navLink'>),
+ )
)}
diff --git a/src/Shared/Components/CollapsibleList/CollapsibleList.types.ts b/src/Shared/Components/CollapsibleList/CollapsibleList.types.ts
index e67b599ff..e3f35d492 100644
--- a/src/Shared/Components/CollapsibleList/CollapsibleList.types.ts
+++ b/src/Shared/Components/CollapsibleList/CollapsibleList.types.ts
@@ -1,7 +1,35 @@
import React from 'react'
import { TippyProps } from '@tippyjs/react'
-export interface CollapsibleListItem {
+interface ButtonTab {
+ /**
+ * Is tab active ( for button tab )
+ */
+ isActive: boolean
+ /**
+ * The callback function to handle click events on the button.
+ */
+ onClick?: (e: React.MouseEvent) => void
+ href?: never
+}
+
+interface NavLinkTab {
+ /**
+ * The URL of the nav link.
+ */
+ href: string
+ /**
+ * The callback function to handle click events on the nav link.
+ */
+ onClick?: (e: React.MouseEvent) => void
+ isActive?: never
+}
+
+export type TabOptions = 'button' | 'navLink'
+
+type ConditionalTabType = TabType extends 'button' ? ButtonTab : NavLinkTab
+
+export type CollapsibleListItem = ConditionalTabType & {
/**
* The title of the list item.
*/
@@ -31,17 +59,9 @@ export interface CollapsibleListItem {
*/
tooltipProps?: TippyProps
}
- /**
- * The URL of the nav link.
- */
- href?: string
- /**
- * The callback function to handle click events on the nav link.
- */
- onClick?: (e: React.MouseEvent) => void
}
-export interface CollapsibleListConfig {
+export interface CollapsibleListConfig {
/**
* The unique identifier for the collapsible list.
*/
@@ -79,18 +99,22 @@ export interface CollapsibleListConfig {
/**
* An array of items to be displayed in the collapsible list.
*/
- items: CollapsibleListItem[]
+ items: CollapsibleListItem[]
/**
* Boolean indicating whether the list is expanded or not.
*/
isExpanded?: boolean
}
-export interface CollapsibleListProps {
+export interface CollapsibleListProps {
/**
* An array of collapsible list configurations.
*/
- config: CollapsibleListConfig[]
+ config: CollapsibleListConfig[]
+ /**
+ * Type of tab list: button or navLink
+ */
+ tabType: TabType
/**
* Function to handle the collapse button click event.
*
diff --git a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiff.types.ts b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiff.types.ts
index f23f7d9c8..a3c5b3519 100644
--- a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiff.types.ts
+++ b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiff.types.ts
@@ -52,13 +52,14 @@ export type DeploymentConfigDiffSelectPickerProps =
selectPickerProps: SelectPickerProps
}
-export interface DeploymentConfigDiffNavigationItem extends Pick {
+export interface DeploymentConfigDiffNavigationItem
+ extends Pick, 'href' | 'title' | 'onClick'> {
Icon?: React.FunctionComponent>
diffState: DeploymentConfigListItem['diffState']
}
export interface DeploymentConfigDiffNavigationCollapsibleItem
- extends Pick {
+ extends Pick, 'id' | 'header' | 'noItemsText'> {
items: DeploymentConfigDiffNavigationItem[]
}
diff --git a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx
index 1211a2901..9028e8ae3 100644
--- a/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx
+++ b/src/Shared/Components/DeploymentConfigDiff/DeploymentConfigDiffNavigation.tsx
@@ -39,28 +39,30 @@ export const DeploymentConfigDiffNavigation = ({
}, [collapsibleNavList])
/** Collapsible List Config. */
- const collapsibleListConfig = collapsibleNavList.map(({ items, ...resListItem }) => ({
- ...resListItem,
- isExpanded: expandedIds[resListItem.id],
- items: items.map(({ diffState, ...resItem }) => ({
- ...resItem,
- strikeThrough: showDetailedDiffState && diffState === DeploymentConfigDiffState.DELETED,
- ...(!hideDiffState && diffState !== DeploymentConfigDiffState.NO_DIFF
- ? {
- iconConfig: {
- Icon: showDetailedDiffState ? diffStateIconMap[diffState] : diffStateIconMap.hasDiff,
- tooltipProps: {
- content: showDetailedDiffState
- ? diffStateTooltipTextMap[diffState]
- : diffStateTooltipTextMap.hasDiff,
- arrow: false,
- placement: 'right' as const,
+ const collapsibleListConfig = collapsibleNavList.map>(
+ ({ items, ...resListItem }) => ({
+ ...resListItem,
+ isExpanded: expandedIds[resListItem.id],
+ items: items.map['items'][0]>(({ diffState, ...resItem }) => ({
+ ...resItem,
+ strikeThrough: showDetailedDiffState && diffState === DeploymentConfigDiffState.DELETED,
+ ...(!hideDiffState && diffState !== DeploymentConfigDiffState.NO_DIFF
+ ? {
+ iconConfig: {
+ Icon: showDetailedDiffState ? diffStateIconMap[diffState] : diffStateIconMap.hasDiff,
+ tooltipProps: {
+ content: showDetailedDiffState
+ ? diffStateTooltipTextMap[diffState]
+ : diffStateTooltipTextMap.hasDiff,
+ arrow: false,
+ placement: 'right' as const,
+ },
},
- },
- }
- : {}),
- })),
- }))
+ }
+ : {}),
+ })),
+ }),
+ )
// METHODS
/** Handles collapse button click. */
@@ -145,7 +147,7 @@ export const DeploymentConfigDiffNavigation = ({
)
})}
-
+
{navHelpText && (
diff --git a/src/Shared/Store/IndexStore.tsx b/src/Shared/Store/IndexStore.tsx
index 8a46e59a9..376345129 100644
--- a/src/Shared/Store/IndexStore.tsx
+++ b/src/Shared/Store/IndexStore.tsx
@@ -17,6 +17,7 @@
/* eslint-disable eqeqeq */
/* eslint-disable array-callback-return */
import { BehaviorSubject } from 'rxjs'
+import { NodeFilters } from '@Shared/Components'
import { AppDetails, AppType, EnvDetails, EnvType, Node, Nodes, PodMetaData, iNode } from '../types'
const _appDetailsSubject: BehaviorSubject = new BehaviorSubject({} as AppDetails)
@@ -43,6 +44,10 @@ const publishFilteredNodes = () => {
return true
}
+ if (_nodeFilter.filterType.toLowerCase() === NodeFilters.drifted && _node.hasDrift) {
+ return true
+ }
+
return false
})
diff --git a/src/Shared/types.ts b/src/Shared/types.ts
index 7ec58c71f..42082ea2e 100644
--- a/src/Shared/types.ts
+++ b/src/Shared/types.ts
@@ -140,6 +140,7 @@ export interface Node {
port: number
canBeHibernated: boolean
isHibernated: boolean
+ hasDrift?: boolean
}
// eslint-disable-next-line no-use-before-define
diff --git a/src/index.ts b/src/index.ts
index 57d0183d5..63ee96403 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -76,6 +76,7 @@ export interface customEnv {
SYSTEM_CONTROLLER_LISTING_TIMEOUT?: number
FEATURE_STEP_WISE_LOGS_ENABLE?: boolean
FEATURE_IMAGE_PROMOTION_ENABLE?: boolean
+ FEATURE_CONFIG_DRIFT_ENABLE: boolean
}
declare global {
interface Window {