From 4f47ef7c0117980da16ef1b95ccd82937a8e8235 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 6 May 2025 12:36:06 +0530 Subject: [PATCH 01/32] feat: enhance AppStatusModal with tab functionality and deployment status handling --- .../AppStatusModal.component.tsx | 61 ++++++++++++-- .../AppStatusModal/AppStatusModalTabList.tsx | 81 +++++++++++++++++++ .../Components/AppStatusModal/service.ts | 43 +++++++--- src/Shared/Components/AppStatusModal/types.ts | 43 +++++++++- .../Components/AppStatusModal/utils.tsx | 39 ++++++++- .../TabGroup/TabGroup.component.tsx | 3 +- .../Components/TabGroup/TabGroup.helpers.tsx | 6 +- .../Components/TabGroup/TabGroup.types.ts | 33 ++++++-- src/Shared/types.ts | 3 + 9 files changed, 280 insertions(+), 32 deletions(-) create mode 100644 src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index d0a0c1947..69b05a4e8 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -12,8 +12,10 @@ import { APIResponseHandler } from '../APIResponseHandler' import { Button, ButtonComponentType, ButtonStyleType, ButtonVariantType } from '../Button' import { Icon } from '../Icon' import { AppStatusBody } from './AppStatusBody' +import AppStatusModalTabList from './AppStatusModalTabList' import { getAppDetails } from './service' -import { AppStatusModalProps } from './types' +import { AppStatusModalProps, AppStatusModalTabType } from './types' +import { getShowDeploymentStatusModal } from './utils' import './AppStatusModal.scss' @@ -22,19 +24,29 @@ const AppStatusModal = ({ handleClose, type, appDetails: appDetailsProp, + deploymentStatusDetailsBreakdownData: deploymentStatusDetailsBreakdownDataProps, + processVirtualEnvironmentDeploymentData, isConfigDriftEnabled, configDriftModal: ConfigDriftModal, appId, envId, + initialTab, }: AppStatusModalProps) => { const [showConfigDriftModal, setShowConfigDriftModal] = useState(false) + const [selectedTab, setSelectedTab] = useState(initialTab || null) const abortControllerRef = useRef(new AbortController()) const pollingTimeoutRef = useRef | null>(null) const getAppDetailsWrapper = async () => { const response = await abortPreviousRequests( - () => getAppDetails(appId, envId, abortControllerRef), + () => + getAppDetails({ + appId, + envId, + abortControllerRef, + deploymentStatusConfig: { showTimeline: false, processVirtualEnvironmentDeploymentData }, + }), abortControllerRef, ) @@ -68,7 +80,11 @@ const AppStatusModal = ({ const areInitialAppDetailsLoadingWithAbortedError = areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) - const appDetails = type === 'release' ? fetchedAppDetails : appDetailsProp + const appDetails = type === 'release' ? fetchedAppDetails?.appDetails : appDetailsProp + const deploymentStatusDetailsBreakdownData = + type === 'release' + ? fetchedAppDetails?.deploymentStatusDetailsBreakdownData + : deploymentStatusDetailsBreakdownDataProps // Adding useEffect to initiate timer for external sync and clear it on unmount useEffect(() => { @@ -105,6 +121,10 @@ const AppStatusModal = ({ setShowConfigDriftModal(false) } + const handleSelectTab = (updatedTab: AppStatusModalTabType) => { + setSelectedTab(updatedTab) + } + if (showConfigDriftModal) { return ( !!segment) + const getEmptyStateMessage = () => { + if (!selectedTab) { + return 'Status is not available' + } + + if (selectedTab === AppStatusModalTabType.APP_STATUS) { + if (!appDetails?.resourceTree) { + return 'Application status is not available' + } + } + + if (!deploymentStatusDetailsBreakdownData || getShowDeploymentStatusModal({ type, appDetails })) { + return 'Deployment status is not available' + } + + return '' + } + const renderContent = () => { - if (!appDetails?.resourceTree) { - return + const emptyStateMessage = getEmptyStateMessage() + + if (emptyStateMessage) { + return } return ( @@ -179,6 +219,17 @@ const AppStatusModal = ({ onClick={handleClose} /> + + {/* TODO: Handle error states */} + {!areInitialAppDetailsLoadingWithAbortedError && !fetchedAppDetailsError && ( + + )}
diff --git a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx new file mode 100644 index 000000000..fd7173692 --- /dev/null +++ b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx @@ -0,0 +1,81 @@ +import { useEffect } from 'react' + +import { AppStatus, DeploymentStatus } from '../StatusComponent' +import { TabGroup, TabProps } from '../TabGroup' +import { AppStatusModalTabListProps, AppStatusModalTabType } from './types' +import { getShowDeploymentStatusModal } from './utils' + +const AppStatusModalTabList = ({ + handleSelectTab, + appDetails, + type, + selectedTab, + deploymentStatusDetailsBreakdownData, +}: AppStatusModalTabListProps) => { + const showDeploymentStatusModal = + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS || + getShowDeploymentStatusModal({ + type, + appDetails, + }) + + const showApplicationStatus = selectedTab === AppStatusModalTabType.APP_STATUS || !appDetails + + const handleSelectAppStatus = () => { + handleSelectTab(AppStatusModalTabType.APP_STATUS) + } + + const handleSelectDeploymentStatusTab = () => { + handleSelectTab(AppStatusModalTabType.DEPLOYMENT_STATUS) + } + + const tabGroups: TabProps[] = [ + ...(showApplicationStatus + ? [ + { + id: AppStatusModalTabType.APP_STATUS, + label: 'Application Status', + tabType: 'button', + props: { + onClick: handleSelectAppStatus, + 'data-testid': 'app-status-tab', + }, + active: selectedTab === AppStatusModalTabType.APP_STATUS, + iconElement: ( + + ), + } satisfies TabProps, + ] + : []), + ...(showDeploymentStatusModal + ? [ + { + id: AppStatusModalTabType.DEPLOYMENT_STATUS, + label: 'Deployment Status', + tabType: 'button', + props: { + onClick: handleSelectDeploymentStatusTab, + 'data-testid': 'deployment-status-tab', + }, + active: selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS, + iconElement: , + } satisfies TabProps, + ] + : []), + ] + + // Could have achieved via onDataLoad but, have done this through useEffect to avoid abrupt shift in case some tabs went missing after polling + useEffect(() => { + if (tabGroups.length) { + handleSelectTab(tabGroups[0]?.id as AppStatusModalTabType) + } + }, []) + + if (!tabGroups.length) { + return null + } + + return +} + +export default AppStatusModalTabList diff --git a/src/Shared/Components/AppStatusModal/service.ts b/src/Shared/Components/AppStatusModal/service.ts index d92addf44..ac342e8ef 100644 --- a/src/Shared/Components/AppStatusModal/service.ts +++ b/src/Shared/Components/AppStatusModal/service.ts @@ -1,33 +1,52 @@ import { get, getIsRequestAborted } from '@Common/API' import { ROUTES } from '@Common/Constants' import { getUrlWithSearchParams, showError } from '@Common/Helper' -import { APIOptions } from '@Common/Types' +import { processDeploymentStatusDetailsData } from '@Shared/Helpers' import { AppDetails, AppType } from '@Shared/types' -export const getAppDetails = async ( - appId: number, - envId: number, - abortControllerRef: APIOptions['abortControllerRef'], -): Promise => { +import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory' +import { GetAppDetailsParamsType } from './types' + +export const getAppDetails = async ({ + appId, + envId, + abortControllerRef, + deploymentStatusConfig, +}: GetAppDetailsParamsType): Promise<{ + appDetails: AppDetails + deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType +}> => { try { const queryParams = getUrlWithSearchParams('', { 'app-id': appId, 'env-id': envId, }) - const [appDetails, resourceTree] = await Promise.all([ - get(`${ROUTES.APP_DETAIL}/v2${queryParams}`, { + const [appDetails, resourceTree, deploymentStatusDetails] = await Promise.all([ + get>(`${ROUTES.APP_DETAIL}/v2${queryParams}`, { abortControllerRef, }), - get(`${ROUTES.APP_DETAIL}/resource-tree${queryParams}`, { + get(`${ROUTES.APP_DETAIL}/resource-tree${queryParams}`, { abortControllerRef, }), + deploymentStatusConfig + ? get( + getUrlWithSearchParams(`${ROUTES.DEPLOYMENT_STATUS}/${appId}/${envId}`, { + showTimeline: deploymentStatusConfig.showTimeline, + }), + ) + : null, ]) return { - ...(appDetails.result || {}), - resourceTree: resourceTree.result, - appType: AppType.DEVTRON_APP, + appDetails: { + ...(appDetails.result || ({} as AppDetails)), + resourceTree: resourceTree.result, + appType: AppType.DEVTRON_APP, + }, + deploymentStatusDetailsBreakdownData: appDetails.result?.isVirtualEnvironment + ? deploymentStatusConfig.processVirtualEnvironmentDeploymentData(deploymentStatusDetails.result) + : processDeploymentStatusDetailsData(deploymentStatusDetails.result), } } catch (error) { if (!getIsRequestAborted(error)) { diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index fd02e2d19..371ba2c57 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -1,24 +1,38 @@ import { FunctionComponent } from 'react' +import { APIOptions } from '@Common/Types' import { AppDetails, ConfigDriftModalProps } from '@Shared/types' +import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory' + +export enum AppStatusModalTabType { + APP_STATUS = 'appStatus', + DEPLOYMENT_STATUS = 'deploymentStatus', +} + export type AppStatusModalProps = { titleSegments: string[] handleClose: () => void - /** - * If given would not poll for app details and resource tree, Polling for gitops timeline would still be done - */ - appDetails?: AppDetails isConfigDriftEnabled: boolean configDriftModal: FunctionComponent } & ( | { type: 'release' + processVirtualEnvironmentDeploymentData: ( + data?: DeploymentStatusDetailsType, + ) => DeploymentStatusDetailsBreakdownDataType + appDetails?: never + deploymentStatusDetailsBreakdownData?: never appId: number envId: number + initialTab?: never } | { type: 'devtron-app' | 'other-apps' | 'stack-manager' + appDetails: AppDetails + deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType + initialTab: AppStatusModalTabType + processVirtualEnvironmentDeploymentData?: never appId?: never envId?: never } @@ -43,3 +57,24 @@ export interface AppStatusContentProps export interface GetFilteredFlattenedNodesFromAppDetailsParamsType extends Pick {} + +/** + * Params for getAppDetails which is called in case of release [i.e, devtron apps] + */ +export interface GetAppDetailsParamsType extends Pick { + appId: number + envId: number + /** + * If given would fetch deploymentConfig + */ + deploymentStatusConfig: { + showTimeline: boolean + processVirtualEnvironmentDeploymentData: AppStatusModalProps['processVirtualEnvironmentDeploymentData'] + } | null +} + +export interface AppStatusModalTabListProps + extends Pick { + handleSelectTab: (updatedTab: AppStatusModalTabType) => void + selectedTab: AppStatusModalTabType +} diff --git a/src/Shared/Components/AppStatusModal/utils.tsx b/src/Shared/Components/AppStatusModal/utils.tsx index 08bc992d5..5d502f26c 100644 --- a/src/Shared/Components/AppStatusModal/utils.tsx +++ b/src/Shared/Components/AppStatusModal/utils.tsx @@ -1,9 +1,14 @@ +import { DeploymentAppTypes } from '@Common/Types' import { DEPLOYMENT_STATUS } from '@Shared/constants' import { aggregateNodes } from '@Shared/Helpers' -import { AppDetails, Node } from '@Shared/types' +import { AppDetails, AppType, Node } from '@Shared/types' +import { ReleaseMode } from '@Pages/index' import { AggregatedNodes, STATUS_SORTING_ORDER } from '../CICDHistory' -import { GetFilteredFlattenedNodesFromAppDetailsParamsType as GetFlattenedNodesFromAppDetailsParamsType } from './types' +import { + AppStatusModalProps, + GetFilteredFlattenedNodesFromAppDetailsParamsType as GetFlattenedNodesFromAppDetailsParamsType, +} from './types' export const getAppStatusMessageFromAppDetails = (appDetails: AppDetails): string => { if (!appDetails?.resourceTree) { @@ -65,3 +70,33 @@ export const getFlattenedNodesFromAppDetails = ({ } export const getResourceKey = (nodeDetails: Node) => `${nodeDetails.kind}/${nodeDetails.name}` + +export const getShowDeploymentStatusModal = ({ + type, + appDetails, +}: Pick): boolean => { + if (!appDetails) { + return false + } + + const isHelmOrDevtronApp = + appDetails.appType === AppType.DEVTRON_APP || appDetails.appType === AppType.DEVTRON_HELM_CHART + + if (type === 'stack-manager' || !isHelmOrDevtronApp) { + return false + } + + if (appDetails.appType === AppType.DEVTRON_HELM_CHART) { + if (!appDetails.lastDeployedTime || appDetails.deploymentAppType === DeploymentAppTypes.HELM) { + return false + } + + return true + } + + if (appDetails.releaseMode === ReleaseMode.MIGRATE_EXTERNAL_APPS && !appDetails?.isPipelineTriggered) { + return false + } + + return true +} diff --git a/src/Shared/Components/TabGroup/TabGroup.component.tsx b/src/Shared/Components/TabGroup/TabGroup.component.tsx index 801a4d806..1affb4a09 100644 --- a/src/Shared/Components/TabGroup/TabGroup.component.tsx +++ b/src/Shared/Components/TabGroup/TabGroup.component.tsx @@ -42,6 +42,7 @@ const Tab = ({ description, shouldWrapTooltip, tooltipProps, + iconElement, }: TabProps & Pick) => { const { tabClassName, iconClassName, badgeClassName } = getClassNameBySizeMap({ hideTopPadding, @@ -63,7 +64,7 @@ const Tab = ({ const content = ( <>

- {getTabIcon({ className: iconClassName, icon, showError, showWarning, size, active })} + {getTabIcon({ className: iconClassName, icon, showError, showWarning, size, active, iconElement })} {label} {getTabBadge(badge, badgeClassName)} {getTabIndicator(showIndicator)} diff --git a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx index 3d515b910..36599186d 100644 --- a/src/Shared/Components/TabGroup/TabGroup.helpers.tsx +++ b/src/Shared/Components/TabGroup/TabGroup.helpers.tsx @@ -28,7 +28,8 @@ export const getTabIcon = ({ className, size, active, -}: Pick & + iconElement, +}: Pick & Pick & { className: string }) => { if (showError) { return @@ -43,6 +44,9 @@ export const getTabIcon = ({ const RenderIcon = icon return } + if (iconElement) { + return iconElement + } return null } diff --git a/src/Shared/Components/TabGroup/TabGroup.types.ts b/src/Shared/Components/TabGroup/TabGroup.types.ts index 0ddb160f9..35cd2db1c 100644 --- a/src/Shared/Components/TabGroup/TabGroup.types.ts +++ b/src/Shared/Components/TabGroup/TabGroup.types.ts @@ -93,6 +93,30 @@ type TabTooltipProps = tooltipProps?: never } +type TabGroupIconProp = + | { + /** + * Icon to be displayed in the tab. + * This can either be a functional component that renders a SVG + * or a string representing the name of the icon to be rendered by the Icon component. + */ + icon: React.FunctionComponent> | IconName + iconElement?: never + } + | { + /** + * Icon to be displayed in the tab. + * This can either be a functional component that renders a SVG + * or a string representing the name of the icon to be rendered by the Icon component. + */ + icon?: never + iconElement: JSX.Element + } + | { + icon?: never + iconElement?: never + } + export type TabProps = { /** * Unique identifier for the tab. @@ -107,12 +131,6 @@ export type TabProps = { * @note - If passed as a `string[]`, it will be rendered with a bullet in-between strings. */ description?: string | string[] - /** - * Icon to be displayed in the tab. - * This can either be a functional component that renders a SVG - * or a string representing the name of the icon to be rendered by the Icon component. - */ - icon?: React.FunctionComponent> | IconName /** * Badge number to be displayed on the tab, typically for notifications. */ @@ -136,7 +154,8 @@ export type TabProps = { */ disabled?: boolean } & ConditionalTabType & - TabTooltipProps + TabTooltipProps & + TabGroupIconProp export interface TabGroupProps { /** diff --git a/src/Shared/types.ts b/src/Shared/types.ts index c752670ac..3d72140da 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -18,6 +18,7 @@ import { ParsedCountry } from 'react-international-phone' import { Dayjs } from 'dayjs' import { APIOptions, ApprovalConfigDataType } from '@Common/Types' +import { ReleaseMode } from '@Pages/index' import { CommonNodeAttr, @@ -245,6 +246,8 @@ export interface AppDetails { chartAvatar?: string fluxTemplateType?: string FluxAppStatusDetail?: FluxAppStatusDetail + isPipelineTriggered?: boolean + releaseMode?: ReleaseMode } export interface ConfigDriftModalProps extends Required> { From b5872cff1b3c57605ca75690cc96ee5526b1c43f Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 7 May 2025 12:05:16 +0530 Subject: [PATCH 02/32] feat: enhance AppStatusModal with deployment status breakdown and tab functionality --- .../AppStatusModal/AppStatusBody.tsx | 98 ++++++--- .../AppStatusModal.component.tsx | 6 +- .../AppStatusModal/AppStatusModalTabList.tsx | 16 +- src/Shared/Components/AppStatusModal/index.ts | 1 + src/Shared/Components/AppStatusModal/types.ts | 4 +- .../CICDHistory/DeploymentDetailSteps.tsx | 4 + .../DeploymentStatusBreakdown.scss | 59 +++++ .../CICDHistory/DeploymentStatusBreakdown.tsx | 75 +++---- .../CICDHistory/DeploymentStatusDetailRow.tsx | 207 +++++++++--------- .../CICDHistory/ErrorInfoStatusBar.tsx | 4 +- .../Components/CICDHistory/cicdHistory.scss | 59 ----- src/Shared/Components/CICDHistory/types.tsx | 9 +- 12 files changed, 301 insertions(+), 241 deletions(-) create mode 100644 src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 18223cd63..834d520b5 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -2,12 +2,13 @@ import { ComponentProps, ReactNode } from 'react' import { Tooltip } from '@Common/Tooltip' +import { DeploymentStatusDetailBreakdown } from '../CICDHistory' import { ErrorBar } from '../Error' import { ShowMoreText } from '../ShowMoreText' -import { AppStatus } from '../StatusComponent' +import { AppStatus, DeploymentStatus } from '../StatusComponent' import AppStatusContent from './AppStatusContent' import { APP_STATUS_CUSTOM_MESSAGES } from './constants' -import { AppStatusBodyProps } from './types' +import { AppStatusBodyProps, AppStatusModalTabType } from './types' import { getAppStatusMessageFromAppDetails } from './utils' const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; value: ReactNode; isLast?: boolean }) => ( @@ -31,39 +32,66 @@ const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; val

) -export const AppStatusBody = ({ appDetails, type, handleShowConfigDriftModal }: AppStatusBodyProps) => { +export const AppStatusBody = ({ + appDetails, + type, + handleShowConfigDriftModal, + deploymentStatusDetailsBreakdownData, + selectedTab, +}: AppStatusBodyProps) => { const appStatus = appDetails.resourceTree?.status?.toUpperCase() || appDetails.appStatus - const message = getAppStatusMessageFromAppDetails(appDetails) - const customMessage = - type === 'stack-manager' - ? 'The installation will complete when status for all the below resources become HEALTHY.' - : APP_STATUS_CUSTOM_MESSAGES[appStatus] - const infoCardItems: (Omit, 'isLast'> & { id: number })[] = [ - { - id: 1, - heading: type !== 'stack-manager' ? 'Application Status' : 'Status', - value: appStatus ? : '--', - }, - ...(message - ? [ - { - id: 2, - heading: 'Message', - value: message, - }, - ] - : []), - ...(customMessage - ? [ + const getAppStatusInfoCardItems = (): (Omit, 'isLast'> & { id: string })[] => { + const message = getAppStatusMessageFromAppDetails(appDetails) + const customMessage = + type === 'stack-manager' + ? 'The installation will complete when status for all the below resources become HEALTHY.' + : APP_STATUS_CUSTOM_MESSAGES[appStatus] + + return [ + { + id: `app-status-${1}`, + heading: type !== 'stack-manager' ? 'Application Status' : 'Status', + value: appStatus ? : '--', + }, + ...(message + ? [ + { + id: `app-status-${2}`, + heading: 'Message', + value: message, + }, + ] + : []), + ...(customMessage + ? [ + { + id: `app-status-${3}`, + heading: 'Message', + value: customMessage, + }, + ] + : []), + ] + } + + const infoCardItems: ReturnType = + selectedTab === AppStatusModalTabType.APP_STATUS + ? getAppStatusInfoCardItems() + : [ { - id: 3, - heading: 'Message', - value: customMessage, + id: `deployment-status-${1}`, + heading: 'Deployment Status', + value: deploymentStatusDetailsBreakdownData?.deploymentStatus ? ( + + ) : ( + '--' + ), }, ] - : []), - ] return (
@@ -83,7 +111,15 @@ export const AppStatusBody = ({ appDetails, type, handleShowConfigDriftModal }: - + {selectedTab === AppStatusModalTabType.APP_STATUS ? ( + + ) : ( + + )}
) } diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 69b05a4e8..a6ef69f82 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -148,7 +148,7 @@ const AppStatusModal = ({ } } - if (!deploymentStatusDetailsBreakdownData || getShowDeploymentStatusModal({ type, appDetails })) { + if (!deploymentStatusDetailsBreakdownData || !getShowDeploymentStatusModal({ type, appDetails })) { return 'Deployment status is not available' } @@ -168,6 +168,8 @@ const AppStatusModal = ({ appDetails={appDetails} type={type} handleShowConfigDriftModal={handleShowConfigDriftModal} + deploymentStatusDetailsBreakdownData={deploymentStatusDetailsBreakdownData} + selectedTab={selectedTab} /> {type === 'stack-manager' && ( @@ -221,7 +223,7 @@ const AppStatusModal = ({ {/* TODO: Handle error states */} - {!areInitialAppDetailsLoadingWithAbortedError && !fetchedAppDetailsError && ( + {!areInitialAppDetailsLoadingWithAbortedError && !fetchedAppDetailsError && fetchedAppDetails && ( + ), } satisfies TabProps, ] @@ -58,7 +62,13 @@ const AppStatusModalTabList = ({ 'data-testid': 'deployment-status-tab', }, active: selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS, - iconElement: , + iconElement: ( + + ), } satisfies TabProps, ] : []), @@ -71,7 +81,7 @@ const AppStatusModalTabList = ({ } }, []) - if (!tabGroups.length) { + if (tabGroups.length <= 1) { return null } diff --git a/src/Shared/Components/AppStatusModal/index.ts b/src/Shared/Components/AppStatusModal/index.ts index 9e35374c8..117f02af6 100644 --- a/src/Shared/Components/AppStatusModal/index.ts +++ b/src/Shared/Components/AppStatusModal/index.ts @@ -1,2 +1,3 @@ export { default as AppStatusContent } from './AppStatusContent' export { default as AppStatusModal } from './AppStatusModal.component' +export { AppStatusModalTabType } from './types' diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index 371ba2c57..aa388cb80 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -38,8 +38,10 @@ export type AppStatusModalProps = { } ) -export interface AppStatusBodyProps extends Required> { +export interface AppStatusBodyProps + extends Required> { handleShowConfigDriftModal: () => void + selectedTab: AppStatusModalTabType } export interface AppStatusContentProps diff --git a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx index 08f56b220..f1cdc89be 100644 --- a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx @@ -17,6 +17,8 @@ import { useEffect, useRef, useState } from 'react' import { useHistory, useParams, useRouteMatch } from 'react-router-dom' +import { IndexStore } from '@Shared/Store' + import { ReactComponent as Arrow } from '../../../Assets/Icon/ic-arrow-forward.svg' import mechanicalOperation from '../../../Assets/Icon/ic-mechanical-operation.svg' import { DeploymentAppTypes, GenericEmptyState, Progressing, URLS } from '../../../Common' @@ -50,6 +52,7 @@ const DeploymentDetailSteps = ({ const [deploymentListLoader, setDeploymentListLoader] = useState( deploymentStatus?.toUpperCase() !== TIMELINE_STATUS.ABORTED, ) + const appDetails = IndexStore.getAppDetails() const isVirtualEnv = useRef(isVirtualEnvironment) const processedData = isVirtualEnv.current && processVirtualEnvironmentDeploymentData @@ -155,6 +158,7 @@ const DeploymentDetailSteps = ({ ) diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss new file mode 100644 index 000000000..f4f1158bf --- /dev/null +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss @@ -0,0 +1,59 @@ +// Have moved the css from AppDetails can refactor later +.deployment-status-breakdown-container, +.deployment-approval-container { + .vertical-connector { + border-left: 1px solid var(--N300); + height: 15px; + position: relative; + left: 18px; + width: 5px; + } + .deployment-status-breakdown-row { + display: flex; + align-items: center; + justify-content: left; + + &.border-collapse { + border-radius: 4px 4px 0 0; + } + } + + .pulse-highlight { + width: 12px; + height: 12px; + border: solid 4px var(--O200); + background-color: var(--O500); + position: relative; + top: 5px; + right: -5px; + border-radius: 50%; + animation-name: pulse; + animation-duration: 2s; + animation-iteration-count: infinite; + animation-timing-function: linear; + } + + .green-tick { + path { + stroke: var(--G500); + } + } + + .app-status-row { + display: grid; + grid-template-columns: 150px 200px 150px auto; + grid-column-gap: 16px; + } + .resource-list { + .app-status-row { + &:hover { + background-color: var(--bg-secondary); + } + } + } + + .detail-tab_border { + border-radius: 0 0 4px 4px; + border-top: 0; + } +} \ No newline at end of file diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index 6172292a7..11ecd342b 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -14,36 +14,47 @@ * limitations under the License. */ +import { Fragment } from 'react' import { useRouteMatch } from 'react-router-dom' import { URLS } from '../../../Common' import { TIMELINE_STATUS } from '../../constants' -import { IndexStore } from '../../Store' import ErrorBar from '../Error/ErrorBar' import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow' import { ErrorInfoStatusBar } from './ErrorInfoStatusBar' -import { DeploymentStatusDetailBreakdownType } from './types' +import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType, ErrorInfoStatusBarType } from './types' + +import './DeploymentStatusBreakdown.scss' const DeploymentStatusDetailBreakdown = ({ deploymentStatusDetailsBreakdownData, isVirtualEnvironment, + appDetails, }: DeploymentStatusDetailBreakdownType) => { - const _appDetails = IndexStore.getAppDetails() const { url } = useRouteMatch() const isHelmManifestPushed = deploymentStatusDetailsBreakdownData.deploymentStatusBreakdown[ TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO ]?.showHelmManifest + + const deploymentStatusDetailRowProps: Pick = + { + appDetails, + deploymentDetailedData: deploymentStatusDetailsBreakdownData, + } + + const errorInfoStatusBarProps: Pick = { + lastFailedStatusType: deploymentStatusDetailsBreakdownData.nonDeploymentError, + errorMessage: deploymentStatusDetailsBreakdownData.deploymentError, + } + return ( <> - {!url.includes(`/${URLS.CD_DETAILS}`) && } -
+ {!url.includes(`/${URLS.CD_DETAILS}`) && } +
{!( isVirtualEnvironment && @@ -52,40 +63,22 @@ const DeploymentStatusDetailBreakdown = ({ ] ) ? ( <> - - - - - - - - + {[TIMELINE_STATUS.GIT_COMMIT, TIMELINE_STATUS.ARGOCD_SYNC, TIMELINE_STATUS.KUBECTL_APPLY].map( + (timelineStatus) => ( + + + + + ), + )} ) : ( @@ -93,13 +86,13 @@ const DeploymentStatusDetailBreakdown = ({ {isHelmManifestPushed && ( )} diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx index c6935bb1c..855d94d0a 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx @@ -20,7 +20,7 @@ import { useParams } from 'react-router-dom' import moment from 'moment' import { ShowMoreText } from '@Shared/Components/ShowMoreText' -import { IndexStore } from '@Shared/Store' +import { AppType } from '@Shared/types' import { ReactComponent as DropDownIcon } from '../../../Assets/Icon/ic-chevron-down.svg' import { DATE_TIME_FORMATS, showError } from '../../../Common' @@ -36,8 +36,12 @@ export const DeploymentStatusDetailRow = ({ type, hideVerticalConnector, deploymentDetailedData, + appDetails, }: DeploymentStatusDetailRowType) => { - const { appId, envId } = useParams<{ appId: string; envId: string }>() + // Won't be available in release, but appDetails will be available in the component in that case + // Can't use appDetails directly as in case of deployment history, appDetails will be null + const { appId: paramAppId, envId: paramEnvId } = useParams<{ appId: string; envId: string }>() + const statusBreakDownType = deploymentDetailedData.deploymentStatusBreakdown[type] const [collapsed, toggleCollapsed] = useState(statusBreakDownType.isCollapsed) @@ -45,14 +49,18 @@ export const DeploymentStatusDetailRow = ({ type === TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO && deploymentDetailedData.deploymentStatus === statusIcon.failed - const appDetails = IndexStore.getAppDetails() - useEffect(() => { toggleCollapsed(statusBreakDownType.isCollapsed) }, [statusBreakDownType.isCollapsed]) - async function manualSyncData() { + const manualSyncData = async () => { try { + const { appId: appDetailsAppId, appType, environmentId: appDetailsEnvId, installedAppId } = appDetails || {} + const parsedAppIdFromAppDetails = appType === AppType.DEVTRON_HELM_CHART ? installedAppId : appDetailsAppId + + const appId = paramAppId || String(parsedAppIdFromAppDetails) + const envId = paramEnvId || String(appDetailsEnvId) + await getManualSync({ appId, envId }) } catch (error) { showError(error) @@ -62,80 +70,86 @@ export const DeploymentStatusDetailRow = ({ toggleCollapsed(!collapsed) } - const renderDetailedData = () => - !collapsed ? ( -
- {statusBreakDownType.timelineStatus && ( -
- {deploymentDetailedData.deploymentStatusBreakdown[type].timelineStatus} - {(deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || - deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) && ( - - Try now - - )} -
- )} - {type === TIMELINE_STATUS.KUBECTL_APPLY && ( -
-
- {deploymentDetailedData.deploymentStatusBreakdown[ - TIMELINE_STATUS.KUBECTL_APPLY - ].kubeList?.map((items, index) => ( + const renderDetailedData = () => { + if (type !== TIMELINE_STATUS.KUBECTL_APPLY) { + return null + } + + return ( +
+
+ {deploymentDetailedData.deploymentStatusBreakdown[TIMELINE_STATUS.KUBECTL_APPLY].kubeList?.map( + (items, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {renderIcon(items.icon)} + {items.message} +
+ ), + )} +
+ {statusBreakDownType.resourceDetails?.length ? ( +
+
+ {MANIFEST_STATUS_HEADERS.map((headerKey, index) => ( // eslint-disable-next-line react/no-array-index-key -
- {renderIcon(items.icon)} - {items.message} +
+ {headerKey}
))}
- {statusBreakDownType.resourceDetails?.length ? ( -
-
- {MANIFEST_STATUS_HEADERS.map((headerKey, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {headerKey} -
- ))} -
-
- {statusBreakDownType.resourceDetails.map((nodeDetails) => ( -
-
{nodeDetails.resourceKind}
-
{nodeDetails.resourceName}
-
- {nodeDetails.resourceStatus} -
- -
- ))} +
+ {statusBreakDownType.resourceDetails.map((nodeDetails) => ( +
+
{nodeDetails.resourceKind}
+
{nodeDetails.resourceName}
+
+ {nodeDetails.resourceStatus} +
+
-
- ) : null} + ))} +
- )} + ) : null}
- ) : null + ) + } - const renderDetailChart = () => - !collapsed && ( + const renderErrorInfoBar = () => ( + + ) + + const isAccordion = + (type === TIMELINE_STATUS.KUBECTL_APPLY && statusBreakDownType.kubeList?.length) || + (type === TIMELINE_STATUS.APP_HEALTH && APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) || + ((type === TIMELINE_STATUS.GIT_COMMIT || type === TIMELINE_STATUS.ARGOCD_SYNC) && + statusBreakDownType.icon === 'failed') + + const renderAccordionDetails = () => { + if (!isAccordion || !collapsed) { + return null + } + + return (
{statusBreakDownType.timelineStatus && (
- {statusBreakDownType.timelineStatus} + {type === TIMELINE_STATUS.APP_HEALTH + ? statusBreakDownType.timelineStatus + : deploymentDetailedData.deploymentStatusBreakdown[type].timelineStatus} + {(deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) && ( + // TODO: Try to make to Button Try now )}
)} -
- -
+ + {type === TIMELINE_STATUS.APP_HEALTH ? ( +
+ +
+ ) : ( + renderDetailedData() + )}
) - - const renderErrorInfoBar = () => ( - - ) + } return ( <>
{renderIcon(statusBreakDownType.icon)} - {statusBreakDownType.displayText} + + {statusBreakDownType.displayText} + {statusBreakDownType.displaySubText && ( @@ -200,11 +214,7 @@ export const DeploymentStatusDetailRow = ({ )} )} - {((type === TIMELINE_STATUS.KUBECTL_APPLY && statusBreakDownType.kubeList?.length) || - (type === TIMELINE_STATUS.APP_HEALTH && - APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) || - ((type === TIMELINE_STATUS.GIT_COMMIT || type === TIMELINE_STATUS.ARGOCD_SYNC) && - statusBreakDownType.icon === 'failed')) && ( + {isAccordion && ( - {type === TIMELINE_STATUS.GIT_COMMIT && renderDetailedData()} - {type === TIMELINE_STATUS.ARGOCD_SYNC && renderDetailedData()} - {type === TIMELINE_STATUS.KUBECTL_APPLY && renderDetailedData()} - {type === TIMELINE_STATUS.APP_HEALTH && renderDetailChart()} + {renderAccordionDetails()} {!hideVerticalConnector &&
} ) diff --git a/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx b/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx index 6a7094766..2fc5d2b1d 100644 --- a/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx +++ b/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx @@ -19,13 +19,13 @@ import { TIMELINE_STATUS } from '../../constants' import { ErrorInfoStatusBarType } from './types' export const ErrorInfoStatusBar = ({ - nonDeploymentError, + lastFailedStatusType, type, errorMessage, hideVerticalConnector, hideErrorIcon, }: ErrorInfoStatusBarType) => - nonDeploymentError === type ? ( + lastFailedStatusType === type ? ( <>
{ type: string hideVerticalConnector?: boolean deploymentDetailedData: DeploymentStatusDetailsBreakdownDataType } export interface ErrorInfoStatusBarType { - nonDeploymentError: string + lastFailedStatusType: string type: string errorMessage: string hideVerticalConnector?: boolean From a693de45c8444a7b520165b9ab9fc5254d1cca0b Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 7 May 2025 14:07:47 +0530 Subject: [PATCH 03/32] feat: add disconnect icon and integrate it into the application; enhance AppStatusModal with loading state handling --- src/Assets/IconV2/ic-disconnect.svg | 3 + .../AppStatusModal.component.tsx | 4 +- src/Shared/Components/AppStatusModal/types.ts | 8 +- .../CICDHistory/DeploymentStatusDetailRow.tsx | 78 +++++++++++-------- src/Shared/Components/CICDHistory/utils.tsx | 33 ++++---- src/Shared/Components/Icon/Icon.tsx | 2 + src/Shared/Components/Icon/IconBase.tsx | 3 +- src/Shared/Components/Icon/types.ts | 1 + 8 files changed, 80 insertions(+), 52 deletions(-) create mode 100644 src/Assets/IconV2/ic-disconnect.svg diff --git a/src/Assets/IconV2/ic-disconnect.svg b/src/Assets/IconV2/ic-disconnect.svg new file mode 100644 index 000000000..358988347 --- /dev/null +++ b/src/Assets/IconV2/ic-disconnect.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index a6ef69f82..23de794e9 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -23,6 +23,7 @@ const AppStatusModal = ({ titleSegments, handleClose, type, + isLoading: isLoadingProp, appDetails: appDetailsProp, deploymentStatusDetailsBreakdownData: deploymentStatusDetailsBreakdownDataProps, processVirtualEnvironmentDeploymentData, @@ -78,7 +79,7 @@ const AppStatusModal = ({ } const areInitialAppDetailsLoadingWithAbortedError = - areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) + isLoadingProp || areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) const appDetails = type === 'release' ? fetchedAppDetails?.appDetails : appDetailsProp const deploymentStatusDetailsBreakdownData = @@ -146,6 +147,7 @@ const AppStatusModal = ({ if (!appDetails?.resourceTree) { return 'Application status is not available' } + return '' } if (!deploymentStatusDetailsBreakdownData || !getShowDeploymentStatusModal({ type, appDetails })) { diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index aa388cb80..76f738b9b 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -21,17 +21,19 @@ export type AppStatusModalProps = { processVirtualEnvironmentDeploymentData: ( data?: DeploymentStatusDetailsType, ) => DeploymentStatusDetailsBreakdownDataType - appDetails?: never - deploymentStatusDetailsBreakdownData?: never appId: number envId: number + appDetails?: never + deploymentStatusDetailsBreakdownData?: never + isLoading?: never initialTab?: never } | { type: 'devtron-app' | 'other-apps' | 'stack-manager' appDetails: AppDetails - deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType + deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType | null initialTab: AppStatusModalTabType + isLoading?: boolean processVirtualEnvironmentDeploymentData?: never appId?: never envId?: never diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx index 855d94d0a..3eef3f00d 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx @@ -24,8 +24,9 @@ import { AppType } from '@Shared/types' import { ReactComponent as DropDownIcon } from '../../../Assets/Icon/ic-chevron-down.svg' import { DATE_TIME_FORMATS, showError } from '../../../Common' -import { DEPLOYMENT_STATUS, statusIcon, TIMELINE_STATUS } from '../../constants' +import { ComponentSizeType, DEPLOYMENT_STATUS, statusIcon, TIMELINE_STATUS } from '../../constants' import { AppStatusContent } from '../AppStatusModal' +import { Button, ButtonStyleType, ButtonVariantType } from '../Button' import { APP_HEALTH_DROP_DOWN_LIST, MANIFEST_STATUS_HEADERS, TERMINAL_STATUS_MAP } from './constants' import { ErrorInfoStatusBar } from './ErrorInfoStatusBar' import { getManualSync } from './service' @@ -163,10 +164,13 @@ export const DeploymentStatusDetailRow = ({ {(deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) && ( - // TODO: Try to make to Button - - Try now - +
)} @@ -186,40 +190,52 @@ export const DeploymentStatusDetailRow = ({ <>
- {renderIcon(statusBreakDownType.icon)} - - - {statusBreakDownType.displayText} +
+ {renderIcon(statusBreakDownType.icon)} + + + {statusBreakDownType.displayText} + + {statusBreakDownType.displaySubText && ( + + {statusBreakDownType.displaySubText} + + )} - {statusBreakDownType.displaySubText && ( + + {statusBreakDownType.time !== '' && statusBreakDownType.icon !== 'inprogress' && ( - {statusBreakDownType.displaySubText} + {moment(statusBreakDownType.time, 'YYYY-MM-DDTHH:mm:ssZ').format( + DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT, + )} )} - - - {statusBreakDownType.time !== '' && statusBreakDownType.icon !== 'inprogress' && ( - - {moment(statusBreakDownType.time, 'YYYY-MM-DDTHH:mm:ssZ').format( - DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT, - )} - - )} +
{isAccordion && ( - + } + ariaLabel="Toggle dropdown" + showAriaLabelInTippy={false} /> )}
diff --git a/src/Shared/Components/CICDHistory/utils.tsx b/src/Shared/Components/CICDHistory/utils.tsx index 0dd44d247..a6157daa1 100644 --- a/src/Shared/Components/CICDHistory/utils.tsx +++ b/src/Shared/Components/CICDHistory/utils.tsx @@ -13,18 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ReactElement } from 'react' +import { ComponentProps, ReactElement } from 'react' import moment from 'moment' import { ReactComponent as ICCheck } from '@Icons/ic-check.svg' -import { ReactComponent as Check } from '@Icons/ic-check-grey.svg' import { ReactComponent as Close } from '@Icons/ic-close.svg' -import { ReactComponent as Disconnect } from '@Icons/ic-disconnected.svg' -import { ReactComponent as Error } from '@Icons/ic-error-exclamation.svg' -import { ReactComponent as ICHelpOutline } from '@Icons/ic-help-outline.svg' import { ReactComponent as ICInProgress } from '@Icons/ic-in-progress.svg' -import { ReactComponent as TimeOut } from '@Icons/ic-timeout-red.svg' -import { ReactComponent as Timer } from '@Icons/ic-timer.svg' import { DATE_TIME_FORMATS } from '@Common/Constants' import { ALL_RESOURCE_KIND_FILTER } from '@Shared/constants' import { isTimeStringAvailable } from '@Shared/Helpers' @@ -123,29 +117,36 @@ export const buildHoverHtmlForWebhook = (eventName, condition, selectors) => { } export const renderIcon = (iconState: string): JSX.Element => { + const iconBaseProps: Pick, 'size' | 'color'> = { + color: null, + size: 20, + } + switch (iconState) { case 'success': - return + return case 'failed': - return + return case 'unknown': - return + return case 'inprogress': return ( -
+
) case 'unreachable': - return + return case 'loading': - return + return case 'disconnect': - return + return case 'time_out': - return + case 'timed_out': + // TODO: Test + return default: - return + return } } diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index 45dfe3db8..494691af9 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -41,6 +41,7 @@ import { ReactComponent as ICDeleteLightning } from '@IconsV2/ic-delete-lightnin import { ReactComponent as ICDelhivery } from '@IconsV2/ic-delhivery.svg' import { ReactComponent as ICDevtron } from '@IconsV2/ic-devtron.svg' import { ReactComponent as ICDevtronHeaderLogo } from '@IconsV2/ic-devtron-header-logo.svg' +import { ReactComponent as ICDisconnect } from '@IconsV2/ic-disconnect.svg' import { ReactComponent as ICDockerhub } from '@IconsV2/ic-dockerhub.svg' import { ReactComponent as ICEcr } from '@IconsV2/ic-ecr.svg' import { ReactComponent as ICEnv } from '@IconsV2/ic-env.svg' @@ -160,6 +161,7 @@ export const iconMap = { 'ic-delhivery': ICDelhivery, 'ic-devtron-header-logo': ICDevtronHeaderLogo, 'ic-devtron': ICDevtron, + 'ic-disconnect': ICDisconnect, 'ic-dockerhub': ICDockerhub, 'ic-ecr': ICEcr, 'ic-env': ICEnv, diff --git a/src/Shared/Components/Icon/IconBase.tsx b/src/Shared/Components/Icon/IconBase.tsx index cb837f9fc..a353c6379 100644 --- a/src/Shared/Components/Icon/IconBase.tsx +++ b/src/Shared/Components/Icon/IconBase.tsx @@ -28,7 +28,7 @@ const conditionalWrap = (tooltipProps: IconBaseProps['tooltipProps']) => (childr ) -export const IconBase = ({ name, iconMap, size = 16, tooltipProps, color }: IconBaseProps) => { +export const IconBase = ({ name, iconMap, size = 16, tooltipProps, color, dataTestId }: IconBaseProps) => { const IconComponent = iconMap[name] if (!IconComponent) { @@ -38,6 +38,7 @@ export const IconBase = ({ name, iconMap, size = 16, tooltipProps, color }: Icon return ( Date: Wed, 7 May 2025 16:53:22 +0530 Subject: [PATCH 04/32] feat: add new icon for external link and integrate it into AppStatusModal; enhance AppStatusBody with app details link functionality --- src/Assets/IconV2/ic-arrow-square-out.svg | 3 + .../AppStatusModal/AppStatusBody.tsx | 57 +++++++++++++++---- .../AppStatusModal.component.tsx | 4 +- .../AppStatusModal/AppStatusModal.scss | 6 ++ .../AppStatusModal/AppStatusModalTabList.tsx | 4 +- src/Shared/Components/Icon/Icon.tsx | 2 + src/Shared/Helpers.tsx | 9 +++ 7 files changed, 71 insertions(+), 14 deletions(-) create mode 100644 src/Assets/IconV2/ic-arrow-square-out.svg diff --git a/src/Assets/IconV2/ic-arrow-square-out.svg b/src/Assets/IconV2/ic-arrow-square-out.svg new file mode 100644 index 000000000..0cf743643 --- /dev/null +++ b/src/Assets/IconV2/ic-arrow-square-out.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 834d520b5..4678f8d0e 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -1,9 +1,13 @@ -import { ComponentProps, ReactNode } from 'react' +import { ComponentProps, PropsWithChildren, ReactNode } from 'react' import { Tooltip } from '@Common/Tooltip' +import { ComponentSizeType } from '@Shared/constants' +import { getAppDetailsURL } from '@Shared/Helpers' +import { Button, ButtonComponentType, ButtonVariantType } from '../Button' import { DeploymentStatusDetailBreakdown } from '../CICDHistory' import { ErrorBar } from '../Error' +import { Icon } from '../Icon' import { ShowMoreText } from '../ShowMoreText' import { AppStatus, DeploymentStatus } from '../StatusComponent' import AppStatusContent from './AppStatusContent' @@ -13,7 +17,7 @@ import { getAppStatusMessageFromAppDetails } from './utils' const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; value: ReactNode; isLast?: boolean }) => (

{heading}

@@ -32,6 +36,31 @@ const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; val
) +const StatusHeadingContainer = ({ + children, + type, + appId, +}: PropsWithChildren> & { appId: number }) => ( +
+ {children} + {type === 'release' ? ( +
+) + export const AppStatusBody = ({ appDetails, type, @@ -52,7 +81,11 @@ export const AppStatusBody = ({ { id: `app-status-${1}`, heading: type !== 'stack-manager' ? 'Application Status' : 'Status', - value: appStatus ? : '--', + value: ( + + {appStatus ? : '--'} + + ), }, ...(message ? [ @@ -82,13 +115,17 @@ export const AppStatusBody = ({ { id: `deployment-status-${1}`, heading: 'Deployment Status', - value: deploymentStatusDetailsBreakdownData?.deploymentStatus ? ( - - ) : ( - '--' + value: ( + + {deploymentStatusDetailsBreakdownData?.deploymentStatus ? ( + + ) : ( + '--' + )} + ), }, ] diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 23de794e9..1d65d6c0b 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -201,8 +201,8 @@ const AppStatusModal = ({ className="flexbox-col dc__content-space h-100 border__primary--left bg__modal--primary shadow__modal app-status-modal" onClick={stopPropagation} > -
-
+
+

{filteredTitleSegments.map((segment, index) => ( diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.scss b/src/Shared/Components/AppStatusModal/AppStatusModal.scss index 101f4c8a0..5dc8ee9ef 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.scss +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.scss @@ -2,4 +2,10 @@ .info-card-item { grid-template-columns: 140px 1fr; } + + &__header { + &:not(:has([role="tablist"])) { + padding-bottom: 12px; + } + } } \ No newline at end of file diff --git a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx index b4655e258..e12c5f627 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx @@ -19,7 +19,7 @@ const AppStatusModalTabList = ({ appDetails, }) - const showApplicationStatus = selectedTab === AppStatusModalTabType.APP_STATUS || !appDetails + const showApplicationStatus = selectedTab === AppStatusModalTabType.APP_STATUS || !!appDetails?.resourceTree const handleSelectAppStatus = () => { handleSelectTab(AppStatusModalTabType.APP_STATUS) @@ -85,7 +85,7 @@ const AppStatusModalTabList = ({ return null } - return + return } export default AppStatusModalTabList diff --git a/src/Shared/Components/Icon/Icon.tsx b/src/Shared/Components/Icon/Icon.tsx index 494691af9..0c9720404 100644 --- a/src/Shared/Components/Icon/Icon.tsx +++ b/src/Shared/Components/Icon/Icon.tsx @@ -7,6 +7,7 @@ import { ReactComponent as ICApica } from '@IconsV2/ic-apica.svg' import { ReactComponent as ICAppGroup } from '@IconsV2/ic-app-group.svg' import { ReactComponent as ICArrowClockwise } from '@IconsV2/ic-arrow-clockwise.svg' import { ReactComponent as ICArrowRight } from '@IconsV2/ic-arrow-right.svg' +import { ReactComponent as ICArrowSquareOut } from '@IconsV2/ic-arrow-square-out.svg' import { ReactComponent as ICArrowsLeftRight } from '@IconsV2/ic-arrows-left-right.svg' import { ReactComponent as ICAther } from '@IconsV2/ic-ather.svg' import { ReactComponent as ICAzure } from '@IconsV2/ic-azure.svg' @@ -127,6 +128,7 @@ export const iconMap = { 'ic-app-group': ICAppGroup, 'ic-arrow-clockwise': ICArrowClockwise, 'ic-arrow-right': ICArrowRight, + 'ic-arrow-square-out': ICArrowSquareOut, 'ic-arrows-left-right': ICArrowsLeftRight, 'ic-ather': ICAther, 'ic-azure': ICAzure, diff --git a/src/Shared/Helpers.tsx b/src/Shared/Helpers.tsx index eda31ea24..69dfe0188 100644 --- a/src/Shared/Helpers.tsx +++ b/src/Shared/Helpers.tsx @@ -46,6 +46,7 @@ import { SortingOrder, SourceTypeMap, TOKEN_COOKIE_NAME, + URLS, UserApprovalConfigType, UserApprovalInfo, ZERO_TIME_STRING, @@ -1078,3 +1079,11 @@ export const getClassNameForStickyHeaderWithShadow = (isStuck: boolean, topClass export const clearCookieOnLogout = () => { document.cookie = `${TOKEN_COOKIE_NAME}=; expires=Thu, 01-Jan-1970 00:00:01 GMT;path=/` } + +export const getAppDetailsURL = (appId: number | string, envId?: number | string): string => { + const baseURL = `${URLS.APP}/${appId}/${URLS.APP_DETAILS}` + if (envId) { + return `${baseURL}/${envId}` + } + return baseURL +} From 4bf5e2c9741b00cd4d84538d393b99e39319ad88 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 7 May 2025 18:35:25 +0530 Subject: [PATCH 05/32] feat: Add new SVG asset for man on rocket and enhance AppStatusModal functionality - Added a new SVG asset `ic-man-on-rocket.svg` to the project. - Updated `AppStatusModal` to handle deployment timeline loading state and improved error handling. - Enhanced the logic for displaying empty states based on deployment status in `AppStatusModal`. - Refactored `AppStatusModalTabList` to safely access deployment status data. - Modified type definitions in `AppStatusModal` to include `isDeploymentTimelineLoading`. - Introduced utility function `getEmptyViewImageFromHelmDeploymentStatus` to return appropriate images based on deployment status. --- src/Assets/Img/ic-celebration.svg | 238 ++++++++++++++++++ src/Assets/Img/ic-man-on-rocket.svg | 94 +++++++ .../AppStatusModal.component.tsx | 46 +++- .../AppStatusModal/AppStatusModalTabList.tsx | 2 +- src/Shared/Components/AppStatusModal/types.ts | 4 +- .../Components/AppStatusModal/utils.tsx | 26 +- 6 files changed, 396 insertions(+), 14 deletions(-) create mode 100644 src/Assets/Img/ic-celebration.svg create mode 100644 src/Assets/Img/ic-man-on-rocket.svg diff --git a/src/Assets/Img/ic-celebration.svg b/src/Assets/Img/ic-celebration.svg new file mode 100644 index 000000000..5c51f12ca --- /dev/null +++ b/src/Assets/Img/ic-celebration.svg @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Assets/Img/ic-man-on-rocket.svg b/src/Assets/Img/ic-man-on-rocket.svg new file mode 100644 index 000000000..70bbd0991 --- /dev/null +++ b/src/Assets/Img/ic-man-on-rocket.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 1d65d6c0b..120d2f3c8 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -5,25 +5,28 @@ import { abortPreviousRequests, getIsRequestAborted } from '@Common/API' import { DISCORD_LINK } from '@Common/Constants' import { Drawer } from '@Common/Drawer' import { GenericEmptyState } from '@Common/EmptyState' -import { stopPropagation, useAsync } from '@Common/Helper' +import { handleUTCTime, stopPropagation, useAsync } from '@Common/Helper' +import { DeploymentAppTypes } from '@Common/Types' import { ComponentSizeType } from '@Shared/constants' import { APIResponseHandler } from '../APIResponseHandler' import { Button, ButtonComponentType, ButtonStyleType, ButtonVariantType } from '../Button' import { Icon } from '../Icon' +import { DeploymentStatus } from '../StatusComponent' import { AppStatusBody } from './AppStatusBody' import AppStatusModalTabList from './AppStatusModalTabList' import { getAppDetails } from './service' import { AppStatusModalProps, AppStatusModalTabType } from './types' -import { getShowDeploymentStatusModal } from './utils' +import { getEmptyViewImageFromHelmDeploymentStatus, getShowDeploymentStatusModal } from './utils' import './AppStatusModal.scss' +// TODO: Need to handleTabChange for appDetails view since polling is external const AppStatusModal = ({ titleSegments, handleClose, type, - isLoading: isLoadingProp, + isDeploymentTimelineLoading, appDetails: appDetailsProp, deploymentStatusDetailsBreakdownData: deploymentStatusDetailsBreakdownDataProps, processVirtualEnvironmentDeploymentData, @@ -78,15 +81,20 @@ const AppStatusModal = ({ } } - const areInitialAppDetailsLoadingWithAbortedError = - isLoadingProp || areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) - const appDetails = type === 'release' ? fetchedAppDetails?.appDetails : appDetailsProp const deploymentStatusDetailsBreakdownData = type === 'release' ? fetchedAppDetails?.deploymentStatusDetailsBreakdownData : deploymentStatusDetailsBreakdownDataProps + const areInitialAppDetailsLoadingWithAbortedError = + areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) + + const isTimelineRequiredAndLoading = + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS && + appDetails?.deploymentAppType !== DeploymentAppTypes.HELM && + isDeploymentTimelineLoading + // Adding useEffect to initiate timer for external sync and clear it on unmount useEffect(() => { if ( @@ -164,6 +172,27 @@ const AppStatusModal = ({ return } + // Empty states for helm based deployment status + if ( + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS && + appDetails.deploymentAppType === DeploymentAppTypes.HELM + ) { + return ( + + Deployment status: + +

+ } + subTitle={`Triggered at ${handleUTCTime(deploymentStatusDetailsBreakdownData.deploymentTriggerTime)} by ${deploymentStatusDetailsBreakdownData.triggeredBy}`} + /> + ) + } + return ( <>
- {/* TODO: Handle error states */} - {!areInitialAppDetailsLoadingWithAbortedError && !fetchedAppDetailsError && fetchedAppDetails && ( + {!areInitialAppDetailsLoadingWithAbortedError && !fetchedAppDetailsError && !!appDetails && ( diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index 76f738b9b..9a0b80aa6 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -25,15 +25,15 @@ export type AppStatusModalProps = { envId: number appDetails?: never deploymentStatusDetailsBreakdownData?: never - isLoading?: never initialTab?: never + isDeploymentTimelineLoading?: never } | { type: 'devtron-app' | 'other-apps' | 'stack-manager' appDetails: AppDetails deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType | null initialTab: AppStatusModalTabType - isLoading?: boolean + isDeploymentTimelineLoading?: boolean processVirtualEnvironmentDeploymentData?: never appId?: never envId?: never diff --git a/src/Shared/Components/AppStatusModal/utils.tsx b/src/Shared/Components/AppStatusModal/utils.tsx index 5d502f26c..d37395764 100644 --- a/src/Shared/Components/AppStatusModal/utils.tsx +++ b/src/Shared/Components/AppStatusModal/utils.tsx @@ -1,4 +1,8 @@ -import { DeploymentAppTypes } from '@Common/Types' +import ICCelebration from '@Images/ic-celebration.svg' +import ICManOnRocket from '@Images/ic-man-on-rocket.svg' +import ICPageNotFound from '@Images/ic-page-not-found.svg' +import NoDeploymentStatusImage from '@Images/no-artifact.webp' +import { DeploymentAppTypes, GenericEmptyStateType } from '@Common/Types' import { DEPLOYMENT_STATUS } from '@Shared/constants' import { aggregateNodes } from '@Shared/Helpers' import { AppDetails, AppType, Node } from '@Shared/types' @@ -94,9 +98,27 @@ export const getShowDeploymentStatusModal = ({ return true } - if (appDetails.releaseMode === ReleaseMode.MIGRATE_EXTERNAL_APPS && !appDetails?.isPipelineTriggered) { + if (appDetails.releaseMode === ReleaseMode.MIGRATE_EXTERNAL_APPS && !appDetails.isPipelineTriggered) { return false } return true } + +export const getEmptyViewImageFromHelmDeploymentStatus = (status: string): GenericEmptyStateType['image'] => { + switch (status?.toLowerCase()) { + case DEPLOYMENT_STATUS.STARTING: + case DEPLOYMENT_STATUS.PROGRESSING: + case DEPLOYMENT_STATUS.INITIATING: + return ICManOnRocket + + case DEPLOYMENT_STATUS.SUCCEEDED: + return ICCelebration + + case DEPLOYMENT_STATUS.FAILED: + return ICPageNotFound + + default: + return NoDeploymentStatusImage + } +} From c1151cac7b50a9328579f665e6a930848afb8093 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 01:37:53 +0530 Subject: [PATCH 06/32] feat: Add DeploymentStatusBreakdown component with types and utility functions - Created new component for deployment status breakdown. - Added types for workflow runner status and deployment status details. - Implemented utility functions to process deployment status details and update timeline data. - Updated constants and types to include new deployment statuses and timeline statuses. - Refactored existing status component to utilize new types. - Cleaned up unused code and imports in Helpers and StatusComponent. --- src/Common/Helper.tsx | 33 +- .../Components/AppStatusModal/service.ts | 10 +- src/Shared/Components/AppStatusModal/types.ts | 9 +- .../CICDHistory/DeploymentDetailSteps.tsx | 12 +- .../CICDHistory/DeploymentStatusBreakdown.tsx | 33 +- .../CICDHistory/DeploymentStatusDetailRow.tsx | 82 ++-- .../CICDHistory/ErrorInfoStatusBar.tsx | 46 -- src/Shared/Components/CICDHistory/types.tsx | 66 +-- src/Shared/Components/CICDHistory/utils.tsx | 87 ++-- .../DeploymentStatusBreakdown/constants.ts | 79 ++++ .../DeploymentStatusBreakdown/index.ts | 3 + .../DeploymentStatusBreakdown/types.ts | 29 ++ .../DeploymentStatusBreakdown/utils.tsx | 359 ++++++++++++++++ .../Components/StatusComponent/utils.ts | 3 +- src/Shared/Components/index.ts | 1 + src/Shared/Helpers.tsx | 403 +----------------- src/Shared/constants.tsx | 22 - src/Shared/types.ts | 123 +++++- 18 files changed, 755 insertions(+), 645 deletions(-) delete mode 100644 src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx create mode 100644 src/Shared/Components/DeploymentStatusBreakdown/constants.ts create mode 100644 src/Shared/Components/DeploymentStatusBreakdown/index.ts create mode 100644 src/Shared/Components/DeploymentStatusBreakdown/types.ts create mode 100644 src/Shared/Components/DeploymentStatusBreakdown/utils.tsx diff --git a/src/Common/Helper.tsx b/src/Common/Helper.tsx index 3939fff3d..b47559608 100644 --- a/src/Common/Helper.tsx +++ b/src/Common/Helper.tsx @@ -17,7 +17,7 @@ import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react' import DOMPurify from 'dompurify' import { JSONPath, JSONPathOptions } from 'jsonpath-plus' -import { compare as compareJSON, applyPatch, unescapePathComponent,deepClone } from 'fast-json-patch' +import { compare as compareJSON, applyPatch, unescapePathComponent, deepClone } from 'fast-json-patch' import { components } from 'react-select' import * as Sentry from '@sentry/browser' import moment from 'moment' @@ -516,7 +516,7 @@ export const getUrlWithSearchParams = ({ @@ -614,7 +614,10 @@ const buildObjectFromPathTokens = (index: number, tokens: string[], value: any) const numberKey = Number(key) const isKeyNumber = !Number.isNaN(numberKey) return isKeyNumber - ? [...Array(numberKey).fill(UNCHANGED_ARRAY_ELEMENT_SYMBOL), buildObjectFromPathTokens(index + 1, tokens, value)] + ? [ + ...Array(numberKey).fill(UNCHANGED_ARRAY_ELEMENT_SYMBOL), + buildObjectFromPathTokens(index + 1, tokens, value), + ] : { [unescapePathComponent(key)]: buildObjectFromPathTokens(index + 1, tokens, value) } } @@ -664,7 +667,12 @@ export const powerSetOfSubstringsFromStart = (strings: string[], regex: RegExp) }) export const convertJSONPointerToJSONPath = (pointer: string) => - unescapePathComponent(pointer.replace(/\/([\*0-9]+)\//g, '[$1].').replace(/\//g, '.').replace(/\./, '$.')) + unescapePathComponent( + pointer + .replace(/\/([\*0-9]+)\//g, '[$1].') + .replace(/\//g, '.') + .replace(/\./, '$.'), + ) export const flatMapOfJSONPaths = ( paths: string[], @@ -1001,7 +1009,7 @@ export const getBranchIcon = (sourceType, _isRegex?: boolean, webhookEventName?: return } if (webhookEventName === WebhookEventNameType.TAG_CREATION) { - return + return } return } @@ -1025,7 +1033,6 @@ export const getIframeWithDefaultAttributes = (iframeString: string, defaultName const parentDiv = document.createElement('div') parentDiv.innerHTML = getSanitizedIframe(iframeString) - const iframe = parentDiv.querySelector('iframe') if (iframe) { if (!iframe.hasAttribute('title') && !!defaultName) { @@ -1094,5 +1101,15 @@ export const getTTLInHumanReadableFormat = (ttl: number): string => { } const humanizedDuration = moment.duration(absoluteTTL, 'seconds').humanize(false) // Since moment.js return "a" or "an" for singular values so replacing with 1. - return humanizedDuration.replace(/^(a|an) /, '1 '); -} \ No newline at end of file + return humanizedDuration.replace(/^(a|an) /, '1 ') +} + +export const findRight = (arr: T[], predicate: (item: T) => boolean): T => { + for (let i = arr.length - 1; i >= 0; i--) { + if (predicate(arr[i])) { + return arr[i] + } + } + + return null +} diff --git a/src/Shared/Components/AppStatusModal/service.ts b/src/Shared/Components/AppStatusModal/service.ts index ac342e8ef..d132a7115 100644 --- a/src/Shared/Components/AppStatusModal/service.ts +++ b/src/Shared/Components/AppStatusModal/service.ts @@ -1,10 +1,14 @@ import { get, getIsRequestAborted } from '@Common/API' import { ROUTES } from '@Common/Constants' import { getUrlWithSearchParams, showError } from '@Common/Helper' -import { processDeploymentStatusDetailsData } from '@Shared/Helpers' -import { AppDetails, AppType } from '@Shared/types' +import { + AppDetails, + AppType, + DeploymentStatusDetailsBreakdownDataType, + DeploymentStatusDetailsType, +} from '@Shared/types' -import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory' +import { processDeploymentStatusDetailsData } from '../DeploymentStatusBreakdown' import { GetAppDetailsParamsType } from './types' export const getAppDetails = async ({ diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index 9a0b80aa6..0e89ba529 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -1,9 +1,12 @@ import { FunctionComponent } from 'react' import { APIOptions } from '@Common/Types' -import { AppDetails, ConfigDriftModalProps } from '@Shared/types' - -import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory' +import { + AppDetails, + ConfigDriftModalProps, + DeploymentStatusDetailsBreakdownDataType, + DeploymentStatusDetailsType, +} from '@Shared/types' export enum AppStatusModalTabType { APP_STATUS = 'appStatus', diff --git a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx index f1cdc89be..697f6cfd5 100644 --- a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx @@ -18,21 +18,19 @@ import { useEffect, useRef, useState } from 'react' import { useHistory, useParams, useRouteMatch } from 'react-router-dom' import { IndexStore } from '@Shared/Store' +import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType, TIMELINE_STATUS } from '@Shared/types' import { ReactComponent as Arrow } from '../../../Assets/Icon/ic-arrow-forward.svg' import mechanicalOperation from '../../../Assets/Icon/ic-mechanical-operation.svg' import { DeploymentAppTypes, GenericEmptyState, Progressing, URLS } from '../../../Common' -import { DEPLOYMENT_STATUS, EMPTY_STATE_STATUS, TIMELINE_STATUS } from '../../constants' -import { getHandleOpenURL, getIsApprovalPolicyConfigured, processDeploymentStatusDetailsData } from '../../Helpers' +import { DEPLOYMENT_STATUS, EMPTY_STATE_STATUS } from '../../constants' +import { getHandleOpenURL, getIsApprovalPolicyConfigured } from '../../Helpers' +import { processDeploymentStatusDetailsData } from '../DeploymentStatusBreakdown' import CDEmptyState from './CDEmptyState' import { DEPLOYMENT_STATUS_QUERY_PARAM } from './constants' import DeploymentStatusDetailBreakdown from './DeploymentStatusBreakdown' import { getDeploymentStatusDetail } from './service' -import { - DeploymentDetailStepsType, - DeploymentStatusDetailsBreakdownDataType, - DeploymentStatusDetailsType, -} from './types' +import { DeploymentDetailStepsType } from './types' let deploymentStatusTimer = null const DeploymentDetailSteps = ({ diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index 11ecd342b..7176f4535 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -17,12 +17,12 @@ import { Fragment } from 'react' import { useRouteMatch } from 'react-router-dom' +import { TIMELINE_STATUS } from '@Shared/types' + import { URLS } from '../../../Common' -import { TIMELINE_STATUS } from '../../constants' import ErrorBar from '../Error/ErrorBar' import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow' -import { ErrorInfoStatusBar } from './ErrorInfoStatusBar' -import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType, ErrorInfoStatusBarType } from './types' +import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType } from './types' import './DeploymentStatusBreakdown.scss' @@ -43,11 +43,6 @@ const DeploymentStatusDetailBreakdown = ({ deploymentDetailedData: deploymentStatusDetailsBreakdownData, } - const errorInfoStatusBarProps: Pick = { - lastFailedStatusType: deploymentStatusDetailsBreakdownData.nonDeploymentError, - errorMessage: deploymentStatusDetailsBreakdownData.deploymentError, - } - return ( <> {!url.includes(`/${URLS.CD_DETAILS}`) && } @@ -63,17 +58,17 @@ const DeploymentStatusDetailBreakdown = ({ ] ) ? ( <> - {[TIMELINE_STATUS.GIT_COMMIT, TIMELINE_STATUS.ARGOCD_SYNC, TIMELINE_STATUS.KUBECTL_APPLY].map( - (timelineStatus) => ( - - - - - ), - )} + {( + [ + TIMELINE_STATUS.GIT_COMMIT, + TIMELINE_STATUS.ARGOCD_SYNC, + TIMELINE_STATUS.KUBECTL_APPLY, + ] as DeploymentStatusDetailRowType['type'][] + ).map((timelineStatus) => ( + + + + ))} () + const [isManualSyncLoading, setIsManualSyncLoading] = useState(false) + const statusBreakDownType = deploymentDetailedData.deploymentStatusBreakdown[type] - const [collapsed, toggleCollapsed] = useState(statusBreakDownType.isCollapsed) + const [isCollapsed, setIsCollapsed] = useState(statusBreakDownType.isCollapsed) const isHelmManifestPushFailed = type === TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO && deploymentDetailedData.deploymentStatus === statusIcon.failed useEffect(() => { - toggleCollapsed(statusBreakDownType.isCollapsed) + setIsCollapsed(statusBreakDownType.isCollapsed) }, [statusBreakDownType.isCollapsed]) const manualSyncData = async () => { try { + setIsManualSyncLoading(true) const { appId: appDetailsAppId, appType, environmentId: appDetailsEnvId, installedAppId } = appDetails || {} const parsedAppIdFromAppDetails = appType === AppType.DEVTRON_HELM_CHART ? installedAppId : appDetailsAppId @@ -65,10 +67,12 @@ export const DeploymentStatusDetailRow = ({ await getManualSync({ appId, envId }) } catch (error) { showError(error) + } finally { + setIsManualSyncLoading(false) } } const toggleDropdown = () => { - toggleCollapsed(!collapsed) + setIsCollapsed(!isCollapsed) } const renderDetailedData = () => { @@ -78,16 +82,15 @@ export const DeploymentStatusDetailRow = ({ return (
-
- {deploymentDetailedData.deploymentStatusBreakdown[TIMELINE_STATUS.KUBECTL_APPLY].kubeList?.map( - (items, index) => ( - // eslint-disable-next-line react/no-array-index-key -
- {renderIcon(items.icon)} - {items.message} -
- ), - )} +
+ {/* TODO: Can be statusBreakDownType */} + {statusBreakDownType.subSteps?.map((items, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+ {renderDeploymentTimelineIcon(items.icon)} + {items.message} +
+ ))}
{statusBreakDownType.resourceDetails?.length ? (
@@ -129,24 +132,30 @@ export const DeploymentStatusDetailRow = ({ ) } - const renderErrorInfoBar = () => ( - - ) + const renderErrorInfoBar = () => { + if (deploymentDetailedData.lastFailedStatusType !== TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO) { + return null + } + + return ( +
+ {deploymentDetailedData.deploymentError} +
    +
  1. Ensure provided repository path is valid
  2. +
  3. Check if credentials provided for OCI registry are valid and have PUSH permission
  4. +
+
+ ) + } const isAccordion = - (type === TIMELINE_STATUS.KUBECTL_APPLY && statusBreakDownType.kubeList?.length) || + statusBreakDownType.subSteps?.length || (type === TIMELINE_STATUS.APP_HEALTH && APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) || ((type === TIMELINE_STATUS.GIT_COMMIT || type === TIMELINE_STATUS.ARGOCD_SYNC) && statusBreakDownType.icon === 'failed') const renderAccordionDetails = () => { - if (!isAccordion || !collapsed) { + if (isCollapsed) { return null } @@ -158,9 +167,7 @@ export const DeploymentStatusDetailRow = ({ statusBreakDownType.icon !== 'inprogress' ? 'bcr-1' : 'bcy-2' }`} > - {type === TIMELINE_STATUS.APP_HEALTH - ? statusBreakDownType.timelineStatus - : deploymentDetailedData.deploymentStatusBreakdown[type].timelineStatus} + {statusBreakDownType.timelineStatus} {(deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) && ( @@ -170,6 +177,7 @@ export const DeploymentStatusDetailRow = ({ variant={ButtonVariantType.text} size={ComponentSizeType.xxs} onClick={manualSyncData} + isLoading={isManualSyncLoading} /> )}
@@ -190,10 +198,10 @@ export const DeploymentStatusDetailRow = ({ <>
- {renderIcon(statusBreakDownType.icon)} + {renderDeploymentTimelineIcon(statusBreakDownType.icon)} {statusBreakDownType.displayText} @@ -208,9 +216,9 @@ export const DeploymentStatusDetailRow = ({ {statusBreakDownType.time !== '' && statusBreakDownType.icon !== 'inprogress' && ( {moment(statusBreakDownType.time, 'YYYY-MM-DDTHH:mm:ssZ').format( DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT, @@ -229,7 +237,7 @@ export const DeploymentStatusDetailRow = ({ diff --git a/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx b/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx deleted file mode 100644 index 2fc5d2b1d..000000000 --- a/src/Shared/Components/CICDHistory/ErrorInfoStatusBar.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 { ReactComponent as Error } from '../../../Assets/Icon/ic-error-exclamation.svg' -import { TIMELINE_STATUS } from '../../constants' -import { ErrorInfoStatusBarType } from './types' - -export const ErrorInfoStatusBar = ({ - lastFailedStatusType, - type, - errorMessage, - hideVerticalConnector, - hideErrorIcon, -}: ErrorInfoStatusBarType) => - lastFailedStatusType === type ? ( - <> -
- {!hideErrorIcon && } - {errorMessage} - {type === TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO && ( -
    -
  1. Ensure provided repository path is valid
  2. -
  3. Check if credentials provided for OCI registry are valid and have PUSH permission
  4. -
- )} -
- {!hideVerticalConnector &&
} - - ) : null diff --git a/src/Shared/Components/CICDHistory/types.tsx b/src/Shared/Components/CICDHistory/types.tsx index 94508461f..5b13a3f40 100644 --- a/src/Shared/Components/CICDHistory/types.tsx +++ b/src/Shared/Components/CICDHistory/types.tsx @@ -34,6 +34,9 @@ import { DeploymentStageType } from '../../constants' import { AggregationKeys, AppDetails, + DeploymentStatusDetailsBreakdownDataType, + DeploymentStatusDetailsType, + DeploymentStatusTimelineType, GitTriggers, Node, NodeType, @@ -371,66 +374,7 @@ export type FinishedType = { artifact: string; type: HistoryComponentType } & ( } ) -export interface SyncStageResourceDetail { - id: number - cdWorkflowRunnerId: number - resourceGroup: string - resourceKind: string - resourceName: string - resourcePhase: string - resourceStatus: string - statusMessage: string -} - -export interface DeploymentStatusDetailsTimelineType { - id: number - cdWorkflowRunnerId: number - status: string - statusDetail: string - statusTime: string - resourceDetails?: SyncStageResourceDetail[] -} -export interface DeploymentStatusDetailsType { - deploymentFinishedOn: string - deploymentStartedOn: string - triggeredBy: string - statusFetchCount: number - statusLastFetchedAt: string - timelines: DeploymentStatusDetailsTimelineType[] - wfrStatus?: string -} - -export interface DeploymentStatusDetailsResponse extends ResponseType { - result?: DeploymentStatusDetailsType -} - -interface DeploymentStatusDetailRow { - icon: string - displayText: string - displaySubText: string - time: string - resourceDetails?: any - isCollapsed?: boolean - kubeList?: { icon: any; message: string }[] - timelineStatus?: string -} -export interface DeploymentStatusDetailsBreakdownDataType { - deploymentStatus: string - deploymentStatusText: string - deploymentTriggerTime: string - deploymentEndTime: string - deploymentError: string - triggeredBy: string - nonDeploymentError: string - deploymentStatusBreakdown: { - DEPLOYMENT_INITIATED: DeploymentStatusDetailRow - GIT_COMMIT?: DeploymentStatusDetailRow - ARGOCD_SYNC?: DeploymentStatusDetailRow - KUBECTL_APPLY?: DeploymentStatusDetailRow - APP_HEALTH?: DeploymentStatusDetailRow - HELM_PACKAGE_GENERATED?: DeploymentStatusDetailRow - } -} +export type DeploymentStatusDetailsResponse = ResponseType export interface DeploymentDetailStepsType { deploymentStatus?: string @@ -577,7 +521,7 @@ export interface DeploymentStatusDetailBreakdownType { } export interface DeploymentStatusDetailRowType extends Pick { - type: string + type: DeploymentStatusTimelineType hideVerticalConnector?: boolean deploymentDetailedData: DeploymentStatusDetailsBreakdownDataType } diff --git a/src/Shared/Components/CICDHistory/utils.tsx b/src/Shared/Components/CICDHistory/utils.tsx index a6157daa1..0568796f8 100644 --- a/src/Shared/Components/CICDHistory/utils.tsx +++ b/src/Shared/Components/CICDHistory/utils.tsx @@ -23,7 +23,7 @@ import { DATE_TIME_FORMATS } from '@Common/Constants' import { ALL_RESOURCE_KIND_FILTER } from '@Shared/constants' import { isTimeStringAvailable } from '@Shared/Helpers' -import { Node, ResourceKindType, WorkflowStatusEnum } from '../../types' +import { DeploymentStatusBreakdownItemType, Node, ResourceKindType, WorkflowStatusEnum } from '../../types' import { Icon } from '../Icon' import { AppStatus, DeploymentStatus, StatusType } from '../StatusComponent' import { @@ -116,40 +116,6 @@ export const buildHoverHtmlForWebhook = (eventName, condition, selectors) => { ) } -export const renderIcon = (iconState: string): JSX.Element => { - const iconBaseProps: Pick, 'size' | 'color'> = { - color: null, - size: 20, - } - - switch (iconState) { - case 'success': - return - case 'failed': - return - case 'unknown': - return - case 'inprogress': - return ( -
-
-
- ) - case 'unreachable': - return - case 'loading': - return - case 'disconnect': - return - case 'time_out': - case 'timed_out': - // TODO: Test - return - default: - return - } -} - export const getStageStatusIcon = (status: StageStatusType): JSX.Element => { switch (status) { case StageStatusType.SUCCESS: @@ -376,3 +342,54 @@ export const getTriggerStatusIcon = (status: string) => { return status } } + +export const renderDeploymentTimelineIcon = (iconState: DeploymentStatusBreakdownItemType['icon']): JSX.Element => { + const iconBaseProps: Pick, 'size' | 'color'> = { + color: null, + size: 20, + } + + switch (iconState) { + case 'success': + return + case 'failed': + return + case 'unknown': + return + case 'inprogress': + return ( +
+
+
+ ) + case 'unreachable': + return + case 'loading': + return + case 'disconnect': + return + case 'time_out': + case 'timed_out': + // TODO: Test + return + default: + return + } +} + +export const getDeploymentTimelineBGColorFromIcon = (icon: DeploymentStatusBreakdownItemType['icon']): string => { + switch (icon) { + case 'success': + return 'bcg-1 cg-7' + case 'failed': + case 'disconnect': + case 'time_out': + case 'timed_out': + return 'bcr-1 cr-5' + case 'inprogress': + case 'loading': + return 'bcy-1 cy-5' + default: + return 'bcn-1 cn-9' + } +} diff --git a/src/Shared/Components/DeploymentStatusBreakdown/constants.ts b/src/Shared/Components/DeploymentStatusBreakdown/constants.ts new file mode 100644 index 000000000..737065370 --- /dev/null +++ b/src/Shared/Components/DeploymentStatusBreakdown/constants.ts @@ -0,0 +1,79 @@ +import { DEPLOYMENT_STATUS } from '@Shared/constants' +import { DeploymentPhaseType, DeploymentStatusTimelineType, TIMELINE_STATUS } from '@Shared/types' + +import { WorkflowRunnerStatusDTO } from './types' + +export const DEPLOYMENT_STATUS_TEXT_MAP: Record<(typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS], string> = { + [DEPLOYMENT_STATUS.SUCCEEDED]: 'Succeeded', + [DEPLOYMENT_STATUS.HEALTHY]: 'Healthy', + [DEPLOYMENT_STATUS.FAILED]: 'Failed', + [DEPLOYMENT_STATUS.TIMED_OUT]: 'Timed out', + // TODO: Add icons + [DEPLOYMENT_STATUS.UNABLE_TO_FETCH]: 'Unable to fetch status', + [DEPLOYMENT_STATUS.INPROGRESS]: 'In progress', + [DEPLOYMENT_STATUS.PROGRESSING]: 'Progressing', + [DEPLOYMENT_STATUS.STARTING]: 'Progressing', + [DEPLOYMENT_STATUS.INITIATING]: 'Progressing', + [DEPLOYMENT_STATUS.SUPERSEDED]: 'Superseded', + [DEPLOYMENT_STATUS.QUEUED]: 'Queued', + [DEPLOYMENT_STATUS.UNKNOWN]: 'Unknown', + [DEPLOYMENT_STATUS.CHECKING]: 'Checking Status', +} + +// Might be more but as per BE its only these for now +export const WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP: Record< + WorkflowRunnerStatusDTO, + (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS] +> = { + [WorkflowRunnerStatusDTO.ABORTED]: DEPLOYMENT_STATUS.FAILED, + [WorkflowRunnerStatusDTO.FAILED]: DEPLOYMENT_STATUS.FAILED, + + [WorkflowRunnerStatusDTO.TIMED_OUT]: DEPLOYMENT_STATUS.TIMED_OUT, + [WorkflowRunnerStatusDTO.UNABLE_TO_FETCH]: DEPLOYMENT_STATUS.UNABLE_TO_FETCH, + + // Degraded means successful deployment + [WorkflowRunnerStatusDTO.DEGRADED]: DEPLOYMENT_STATUS.SUCCEEDED, + [WorkflowRunnerStatusDTO.HEALTHY]: DEPLOYMENT_STATUS.SUCCEEDED, + [WorkflowRunnerStatusDTO.SUCCEEDED]: DEPLOYMENT_STATUS.SUCCEEDED, + + [WorkflowRunnerStatusDTO.INITIATING]: DEPLOYMENT_STATUS.INITIATING, + [WorkflowRunnerStatusDTO.STARTING]: DEPLOYMENT_STATUS.STARTING, + [WorkflowRunnerStatusDTO.PROGRESSING]: DEPLOYMENT_STATUS.INPROGRESS, + + [WorkflowRunnerStatusDTO.QUEUED]: DEPLOYMENT_STATUS.QUEUED, +} + +export const PROGRESSING_DEPLOYMENT_STATUS: (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS][] = [ + DEPLOYMENT_STATUS.INPROGRESS, + DEPLOYMENT_STATUS.PROGRESSING, + DEPLOYMENT_STATUS.STARTING, + DEPLOYMENT_STATUS.INITIATING, + DEPLOYMENT_STATUS.CHECKING, +] + +export const SUCCESSFUL_DEPLOYMENT_STATUS: typeof PROGRESSING_DEPLOYMENT_STATUS = [ + DEPLOYMENT_STATUS.SUCCEEDED, + DEPLOYMENT_STATUS.HEALTHY, +] + +export const FAILED_DEPLOYMENT_STATUS: typeof PROGRESSING_DEPLOYMENT_STATUS = [ + DEPLOYMENT_STATUS.FAILED, + DEPLOYMENT_STATUS.TIMED_OUT, + DEPLOYMENT_STATUS.UNABLE_TO_FETCH, +] + +export const PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER: Readonly = [ + TIMELINE_STATUS.DEPLOYMENT_INITIATED, + TIMELINE_STATUS.GIT_COMMIT, + TIMELINE_STATUS.ARGOCD_SYNC, + TIMELINE_STATUS.KUBECTL_APPLY, + TIMELINE_STATUS.APP_HEALTH, +] + +export const DEPLOYMENT_PHASES: Readonly = [ + DeploymentPhaseType.PRE_SYNC, + DeploymentPhaseType.SYNC, + DeploymentPhaseType.POST_SYNC, + DeploymentPhaseType.SKIP, + DeploymentPhaseType.SYNC_FAIL, +] diff --git a/src/Shared/Components/DeploymentStatusBreakdown/index.ts b/src/Shared/Components/DeploymentStatusBreakdown/index.ts new file mode 100644 index 000000000..6eec6bb5c --- /dev/null +++ b/src/Shared/Components/DeploymentStatusBreakdown/index.ts @@ -0,0 +1,3 @@ +export * from './constants' +export * from './types' +export * from './utils' diff --git a/src/Shared/Components/DeploymentStatusBreakdown/types.ts b/src/Shared/Components/DeploymentStatusBreakdown/types.ts new file mode 100644 index 000000000..8176a4a0e --- /dev/null +++ b/src/Shared/Components/DeploymentStatusBreakdown/types.ts @@ -0,0 +1,29 @@ +import { DEPLOYMENT_STATUS } from '@Shared/constants' +import { + DeploymentStatusBreakdownItemType, + DeploymentStatusDetailsType, + DeploymentStatusTimelineType, +} from '@Shared/types' + +export enum WorkflowRunnerStatusDTO { + PROGRESSING = 'Progressing', + ABORTED = 'Aborted', + FAILED = 'Failed', + SUCCEEDED = 'Succeeded', + TIMED_OUT = 'TimedOut', + UNABLE_TO_FETCH = 'UnableToFetch', + STARTING = 'Starting', + QUEUED = 'Queued', + INITIATING = 'Initiating', + // Not found on BE but for Backward compatibility + HEALTHY = 'Healthy', + DEGRADED = 'Degraded', +} + +export interface HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType { + timelineData: DeploymentStatusBreakdownItemType + timelineStatusType: DeploymentStatusTimelineType + deploymentStatus: typeof DEPLOYMENT_STATUS.UNABLE_TO_FETCH | typeof DEPLOYMENT_STATUS.TIMED_OUT + statusLastFetchedAt: DeploymentStatusDetailsType['statusLastFetchedAt'] | null + statusFetchCount: DeploymentStatusDetailsType['statusFetchCount'] | null +} diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx new file mode 100644 index 000000000..d4fbc1f62 --- /dev/null +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -0,0 +1,359 @@ +/* eslint-disable no-param-reassign */ +import { findRight, handleUTCTime, logExceptionToSentry } from '@Common/Helper' +import { DEPLOYMENT_STATUS } from '@Shared/constants' +import { + DeploymentPhaseType, + DeploymentStatusBreakdownItemType, + DeploymentStatusDetailsBreakdownDataType, + DeploymentStatusDetailsTimelineType, + DeploymentStatusDetailsType, + DeploymentStatusTimelineType, + TIMELINE_STATUS, +} from '@Shared/types' + +import { + DEPLOYMENT_PHASES, + FAILED_DEPLOYMENT_STATUS, + PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER, + PROGRESSING_DEPLOYMENT_STATUS, + SUCCESSFUL_DEPLOYMENT_STATUS, + WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP, +} from './constants' +import { HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType } from './types' + +const getDefaultDeploymentStatusTimeline = ( + data?: DeploymentStatusDetailsType, +): DeploymentStatusDetailsBreakdownDataType => { + const commonProps: Pick< + DeploymentStatusBreakdownItemType, + 'displaySubText' | 'time' | 'isCollapsed' | 'timelineStatus' | 'icon' + > = { + icon: '', + displaySubText: '', + timelineStatus: '', + time: '', + isCollapsed: true, + } + + return { + deploymentStatus: WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP[data?.wfrStatus] || DEPLOYMENT_STATUS.INPROGRESS, + deploymentTriggerTime: data?.deploymentStartedOn || '', + deploymentEndTime: data?.deploymentFinishedOn || '', + deploymentError: '', + triggeredBy: data?.triggeredBy || '', + lastFailedStatusType: '', + deploymentStatusBreakdown: { + [TIMELINE_STATUS.DEPLOYMENT_INITIATED]: { + ...commonProps, + icon: 'success', + displayText: `Deployment initiated ${data?.triggeredBy ? `by ${data.triggeredBy}` : ''}`, + }, + [TIMELINE_STATUS.GIT_COMMIT]: { + ...commonProps, + displayText: 'Push manifest to Git', + }, + [TIMELINE_STATUS.ARGOCD_SYNC]: { + ...commonProps, + displayText: 'Synced with Argo CD', + }, + [TIMELINE_STATUS.KUBECTL_APPLY]: { + ...commonProps, + displayText: 'Apply manifest to Kubernetes', + resourceDetails: [], + subSteps: [], + }, + [TIMELINE_STATUS.APP_HEALTH]: { + ...commonProps, + displayText: 'Propagate manifest to Kubernetes resources', + }, + }, + } +} + +const getPredicate = + (timelineStatus: DeploymentStatusTimelineType) => + (timelineItem: DeploymentStatusDetailsTimelineType): boolean => { + switch (timelineStatus) { + case TIMELINE_STATUS.DEPLOYMENT_INITIATED: + return timelineStatus === timelineItem.status + + case TIMELINE_STATUS.GIT_COMMIT: + return timelineItem.status.includes(TIMELINE_STATUS.GIT_COMMIT) + + case TIMELINE_STATUS.ARGOCD_SYNC: + return timelineItem.status.includes(TIMELINE_STATUS.ARGOCD_SYNC) + + case TIMELINE_STATUS.KUBECTL_APPLY: + return timelineItem.status.includes(TIMELINE_STATUS.KUBECTL_APPLY) + + case TIMELINE_STATUS.APP_HEALTH: + return [TIMELINE_STATUS.HEALTHY, TIMELINE_STATUS.DEGRADED, TIMELINE_STATUS.DEPLOYMENT_FAILED].includes( + timelineItem.status as TIMELINE_STATUS, + ) + + default: + return false + } + } + +const handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus = ({ + timelineData, + timelineStatusType, + deploymentStatus, + statusLastFetchedAt, + statusFetchCount, +}: HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType) => { + timelineData.icon = deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH ? 'disconnect' : 'timed_out' + timelineData.displaySubText = 'Unknown' + timelineData.isCollapsed = false + + const lastFetchedTime = statusLastFetchedAt ? handleUTCTime(statusLastFetchedAt, true) : '--' + const resourceError = `Below resources did not become healthy within 10 mins. Resource status shown below was last fetched ${lastFetchedTime}. ${statusFetchCount || '--'} retries failed.` + timelineData.timelineStatus = [TIMELINE_STATUS.KUBECTL_APPLY, TIMELINE_STATUS.APP_HEALTH].includes( + timelineStatusType, + ) + ? resourceError + : '' +} + +const processKubeCTLApply = ( + timelineData: DeploymentStatusBreakdownItemType, + element: DeploymentStatusDetailsTimelineType, + deploymentStatus: DeploymentStatusDetailsBreakdownDataType['deploymentStatus'], + data?: DeploymentStatusDetailsType, +) => { + const tableData: { + currentPhase: DeploymentPhaseType | '' + currentTableData: DeploymentStatusBreakdownItemType['subSteps'] + } = { + currentPhase: '', + currentTableData: [{ icon: 'success', message: 'Started by Argo CD' }], + } + + if (element.resourceDetails) { + // Used to parse resource details base struct with current phase as last phase + DEPLOYMENT_PHASES.forEach((phase) => { + element.resourceDetails.forEach((item) => { + if (phase === item.resourcePhase) { + tableData.currentPhase = phase + tableData.currentTableData.push({ + icon: 'success', + phase, + message: `${phase}: Create and update resources based on manifest`, + }) + } + }) + }) + } + + if (element.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED) { + timelineData.resourceDetails = element.resourceDetails?.filter( + (item) => item.resourcePhase === tableData.currentPhase, + ) + + if (PROGRESSING_DEPLOYMENT_STATUS.includes(deploymentStatus)) { + timelineData.icon = 'inprogress' + timelineData.displaySubText = 'In progress' + timelineData.time = element.statusTime + timelineData.timelineStatus = element.statusDetail + timelineData.isCollapsed = false + timelineData.subSteps = tableData.currentTableData.map((item) => ({ + icon: item.phase === tableData.currentPhase ? 'loading' : 'success', + message: item.message, + })) + return + } + + if (deploymentStatus === DEPLOYMENT_STATUS.FAILED) { + timelineData.icon = 'unknown' + timelineData.displaySubText = ': Unknown' + return + } + + if ( + deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || + deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH + ) { + handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus({ + timelineData, + timelineStatusType: TIMELINE_STATUS.KUBECTL_APPLY, + deploymentStatus, + statusLastFetchedAt: data?.statusLastFetchedAt, + statusFetchCount: data?.statusFetchCount, + }) + timelineData.subSteps = tableData.currentTableData.map((item) => ({ + icon: item.phase === tableData.currentPhase ? 'failed' : 'success', + message: item.message, + })) + return + } + + return + } + + if (element.status === TIMELINE_STATUS.KUBECTL_APPLY_SYNCED) { + timelineData.resourceDetails = [] + timelineData.displaySubText = '' + timelineData.time = element.statusTime + timelineData.icon = 'success' + timelineData.subSteps = tableData.currentTableData + } +} + +export const processDeploymentStatusDetailsData = ( + data?: DeploymentStatusDetailsType, +): DeploymentStatusDetailsBreakdownDataType => { + if (data && !WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP[data.wfrStatus]) { + logExceptionToSentry(new Error(`New WFR status found: ${data?.wfrStatus}`)) + } + const deploymentData = getDefaultDeploymentStatusTimeline(data) + + const { deploymentStatus } = deploymentData + + if (!data?.timelines) { + if (SUCCESSFUL_DEPLOYMENT_STATUS.includes(deploymentStatus)) { + Object.values(deploymentData.deploymentStatusBreakdown).forEach((value) => { + value.icon = 'success' + }) + } else if (FAILED_DEPLOYMENT_STATUS.includes(deploymentStatus)) { + deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Failed' + } + + return deploymentData + } + + // Would move for each timeline iteratively and if timeline is in terminal state then early return + // If timeline is in non-terminal state then we mark it as waiting + if (!data?.timelines?.length) { + return deploymentData + } + + const isProgressing = PROGRESSING_DEPLOYMENT_STATUS.includes(deploymentStatus) + const isArgoCDAvailable = data.timelines.some((timeline) => timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC)) + + // After initial processing will mark all unavailable timelines [present before last invalid state] as success + PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.forEach((timelineStatusType, index) => { + // TODO: Confirm for findRight approach is fine? since, for e.g, i believe apply start should be before apply synced + const element = findRight(data.timelines, getPredicate(timelineStatusType)) + const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] + + if (!element) { + // This means the the last timeline is progressing or waiting so obviously this timeline is also waiting + if (isProgressing && timelineData.icon !== 'inprogress') { + timelineData.icon = '' + timelineData.displaySubText = 'Waiting' + } + + if (isProgressing && timelineStatusType === TIMELINE_STATUS.KUBECTL_APPLY) { + timelineData.subSteps = [ + { icon: '', message: 'Waiting to be started by Argo CD' }, + { icon: '', message: 'Create and update resources based on manifest' }, + ] + timelineData.isCollapsed = false + } + + if (deploymentStatus === DEPLOYMENT_STATUS.FAILED) { + timelineData.displaySubText = '' + timelineData.icon = 'unreachable' + } + + if ( + (deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH || + deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) && + timelineData.icon === 'inprogress' + ) { + handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus({ + timelineData, + timelineStatusType, + deploymentStatus, + statusLastFetchedAt: data?.statusLastFetchedAt, + statusFetchCount: data?.statusFetchCount, + }) + } + + return + } + + switch (timelineStatusType) { + case TIMELINE_STATUS.DEPLOYMENT_INITIATED: + case TIMELINE_STATUS.GIT_COMMIT: + case TIMELINE_STATUS.ARGOCD_SYNC: + timelineData.time = element.statusTime + timelineData.icon = 'success' + + // These are singular events so either their success will come or failure + if ( + [TIMELINE_STATUS.GIT_COMMIT_FAILED, TIMELINE_STATUS.ARGOCD_SYNC_FAILED].includes( + element.status as TIMELINE_STATUS, + ) + ) { + timelineData.displaySubText = 'Failed' + timelineData.icon = 'failed' + timelineData.isCollapsed = false + timelineData.timelineStatus = element.statusDetail + // Not handling the next timelines here since would be handled in the next iteration through deploymentStatus; + // So Assumption is deploymentStatus is going to Failed in this case + } + break + + case TIMELINE_STATUS.KUBECTL_APPLY: { + if (!isArgoCDAvailable) { + deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' + deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' + deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime + } + + processKubeCTLApply(timelineData, element, deploymentStatus, data) + break + } + + case TIMELINE_STATUS.APP_HEALTH: + timelineData.time = element.statusTime + if (element.status === TIMELINE_STATUS.DEPLOYMENT_FAILED) { + // TODO: Check why its icon is not failed in earlier implementation + timelineData.icon = 'failed' + timelineData.displaySubText = 'Failed' + timelineData.timelineStatus = element.statusDetail + break + } + + if (element.status === TIMELINE_STATUS.HEALTHY || element.status === TIMELINE_STATUS.DEGRADED) { + timelineData.icon = 'success' + timelineData.displaySubText = element.status === TIMELINE_STATUS.HEALTHY ? '' : 'Degraded' + } + break + + default: + break + } + + // TODO: Should we add in progress check here? + if (timelineData.icon === 'success' && index !== PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.length - 1) { + // Moving the next timeline to inprogress + const nextTimelineStatus = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[index + 1] + const nextTimeline = deploymentData.deploymentStatusBreakdown[nextTimelineStatus] + + nextTimeline.icon = 'inprogress' + nextTimeline.displaySubText = 'In progress' + } + }) + + // Traversing the timeline in reverse order so that if any status is there which is inprogress or success then we will mark all the previous steps as success + for (let i = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.length - 1; i >= 0; i -= 1) { + const timelineStatusType = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[i] + const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] + + if (timelineData.icon === 'inprogress' || timelineData.icon === 'success') { + // If the timeline is in progress or success then we will mark all the previous steps as success + for (let j = i - 1; j >= 0; j -= 1) { + const prevTimelineStatusType = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[j] + const prevTimelineData = deploymentData.deploymentStatusBreakdown[prevTimelineStatusType] + prevTimelineData.icon = 'success' + prevTimelineData.displaySubText = '' + } + break + } + } + + return deploymentData +} diff --git a/src/Shared/Components/StatusComponent/utils.ts b/src/Shared/Components/StatusComponent/utils.ts index eddd95a1e..6c8d781fa 100644 --- a/src/Shared/Components/StatusComponent/utils.ts +++ b/src/Shared/Components/StatusComponent/utils.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { TIMELINE_STATUS } from '@Shared/constants' -import { WorkflowStatusEnum } from '@Shared/types' +import { TIMELINE_STATUS, WorkflowStatusEnum } from '@Shared/types' import { IconName, IconsProps } from '../Icon' import { StatusType } from './types' diff --git a/src/Shared/Components/index.ts b/src/Shared/Components/index.ts index b45c2a4ac..ddbbf628d 100644 --- a/src/Shared/Components/index.ts +++ b/src/Shared/Components/index.ts @@ -40,6 +40,7 @@ export * from './CountrySelect' export * from './CustomInput' export * from './DatePicker' export * from './DeploymentConfigDiff' +export * from './DeploymentStatusBreakdown' export * from './DetectBottom' export * from './DiffViewer' export * from './DynamicDataTable' diff --git a/src/Shared/Helpers.tsx b/src/Shared/Helpers.tsx index 69dfe0188..f5f2011ad 100644 --- a/src/Shared/Helpers.tsx +++ b/src/Shared/Helpers.tsx @@ -36,7 +36,6 @@ import { MaterialHistoryType } from '@Shared/Services/app.types' import { ApprovalConfigDataType, DATE_TIME_FORMATS, - handleUTCTime, ManualApprovalType, mapByKey, MaterialInfo, @@ -52,13 +51,8 @@ import { ZERO_TIME_STRING, } from '../Common' import { getAggregator } from '../Pages' -import { - AggregatedNodes, - DeploymentStatusDetailsBreakdownDataType, - DeploymentStatusDetailsType, - PodMetadatum, -} from './Components' -import { DEPLOYMENT_STATUS, TIMELINE_STATUS, UNSAVED_CHANGES_PROMPT_MESSAGE } from './constants' +import { AggregatedNodes, PodMetadatum } from './Components' +import { UNSAVED_CHANGES_PROMPT_MESSAGE } from './constants' import { AggregationKeys, BorderConfigType, @@ -307,399 +301,6 @@ export const renderValidInputButtonTippy = (children: ReactElement) => ( ) -// NOTE: Need to improve logic since in some cases the unknown status would leak to previous entites, can do that by not setting deploymentStatus as Failed by ourselves and let backend be source of truth of that -export const processDeploymentStatusDetailsData = ( - data?: DeploymentStatusDetailsType, -): DeploymentStatusDetailsBreakdownDataType => { - const deploymentData = { - deploymentStatus: 'inprogress', - deploymentStatusText: 'In progress', - deploymentTriggerTime: data?.deploymentStartedOn || '', - deploymentEndTime: data?.deploymentFinishedOn || '', - deploymentError: '', - triggeredBy: data?.triggeredBy || '', - nonDeploymentError: '', - deploymentStatusBreakdown: { - DEPLOYMENT_INITIATED: { - icon: 'success', - displayText: `Deployment initiated ${data?.triggeredBy ? `by ${data?.triggeredBy}` : ''}`, - displaySubText: '', - time: '', - }, - GIT_COMMIT: { - icon: '', - displayText: 'Push manifest to Git', - displaySubText: '', - timelineStatus: '', - time: '', - isCollapsed: true, - }, - ARGOCD_SYNC: { - icon: '', - displayText: 'Synced with Argo CD', - displaySubText: '', - timelineStatus: '', - time: '', - isCollapsed: true, - }, - KUBECTL_APPLY: { - icon: '', - displayText: 'Apply manifest to Kubernetes', - timelineStatus: '', - displaySubText: '', - time: '', - resourceDetails: [], - isCollapsed: true, - kubeList: [], - }, - APP_HEALTH: { - icon: '', - displayText: 'Propogate manifest to Kubernetes resources', - timelineStatus: '', - displaySubText: '', - time: '', - isCollapsed: true, - }, - }, - } - - const lastFetchedTime = handleUTCTime(data?.statusLastFetchedAt, true) - const deploymentPhases = ['PreSync', 'Sync', 'PostSync', 'Skip', 'SyncFail'] - const tableData: { currentPhase: string; currentTableData: { icon: string; phase?: string; message: string }[] } = { - currentPhase: '', - currentTableData: [{ icon: 'success', message: 'Started by Argo CD' }], - } - - // data when timelines is available - if (data?.timelines?.length) { - // TO Support legacy data have to make sure that if ARGOCD_SYNC is not available then we fill it with dummy values - const isArgoCDAvailable = data.timelines.some((timeline) => - timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC), - ) - - for (let index = data.timelines.length - 1; index >= 0; index--) { - const element = data.timelines[index] - if (element.status === TIMELINE_STATUS.HEALTHY || element.status === TIMELINE_STATUS.DEGRADED) { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUCCEEDED - deploymentData.deploymentStatusText = 'Succeeded' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = - element.status === TIMELINE_STATUS.HEALTHY ? '' : 'Degraded' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.time = element.statusTime - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = true - deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = true - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success' - } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_FAILED) { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED - deploymentData.deploymentStatusText = 'Failed' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ' Failed' - deploymentData.deploymentError = element.statusDetail - } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_SUPERSEDED) { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUPERSEDED - } else if ( - index === data.timelines.length - 1 && - (element.status === TIMELINE_STATUS.FETCH_TIMED_OUT || - element.status === TIMELINE_STATUS.UNABLE_TO_FETCH_STATUS) - ) { - if (element.status === TIMELINE_STATUS.FETCH_TIMED_OUT) { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.TIMED_OUT - deploymentData.deploymentStatusText = 'Timed out' - } else if (element.status === TIMELINE_STATUS.UNABLE_TO_FETCH_STATUS) { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.UNABLE_TO_FETCH - deploymentData.deploymentStatusText = 'Unable to fetch status' - } - deploymentData.deploymentError = `Below resources did not become healthy within 10 mins. Resource status shown below was last fetched ${lastFetchedTime}. ${data.statusFetchCount} retries failed.` - } else if (element.status.includes(TIMELINE_STATUS.KUBECTL_APPLY)) { - if (!isArgoCDAvailable) { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime - } - - if (element?.resourceDetails) { - deploymentPhases.forEach((phase) => { - // eslint-disable-next-line no-restricted-syntax - for (const item of element.resourceDetails) { - if (phase === item.resourcePhase) { - tableData.currentPhase = phase - // Seems else block was forgotten to add here, TODO: Sync for this later - // eslint-disable-next-line no-empty - if (item.resourceStatus === 'failed') { - } - tableData.currentTableData.push({ - icon: 'success', - phase, - message: `${phase}: Create and update resources based on manifest`, - }) - return - } - } - }) - } - if ( - element.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED && - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time === '' && - deploymentData.deploymentStatus !== DEPLOYMENT_STATUS.SUCCEEDED - ) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.resourceDetails = - element.resourceDetails?.filter((item) => item.resourcePhase === tableData.currentPhase) - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = ': Unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown' - } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.SUCCEEDED) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - } else if ( - deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || - deploymentData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH - ) { - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false - } else { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'disconnect' - } - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.timelineStatus = - deploymentData.deploymentError - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = - tableData.currentTableData.map((item) => ({ - icon: item.phase === tableData.currentPhase ? 'failed' : 'success', - message: item.message, - })) - } else { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'inprogress' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'In progress' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time = element.statusTime - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.timelineStatus = element.statusDetail - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = - tableData.currentTableData.map((item) => ({ - icon: item.phase === tableData.currentPhase ? 'loading' : 'success', - message: item.message, - })) - } - } else if (element.status === TIMELINE_STATUS.KUBECTL_APPLY_SYNCED) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.resourceDetails = [] - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time = element.statusTime - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = tableData.currentTableData - - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS) { - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'inprogress' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false - } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'failed' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Failed' - } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) { - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'timed_out' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.timelineStatus = - deploymentData.deploymentError - deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false - } else if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) { - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'disconnect' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.timelineStatus = - deploymentData.deploymentError - deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = false - } - } - } else if (element.status.includes(TIMELINE_STATUS.ARGOCD_SYNC)) { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime - - if (element.status === TIMELINE_STATUS.ARGOCD_SYNC_FAILED) { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Failed' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'failed' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.isCollapsed = false - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED - deploymentData.deploymentStatusText = 'Failed' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.timelineStatus = element.statusDetail - } else { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - if (deploymentData.nonDeploymentError === '') { - deploymentData.nonDeploymentError = TIMELINE_STATUS.KUBECTL_APPLY - } - } else if ( - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.time === '' && - deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS - ) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [ - { icon: '', message: 'Waiting to be started by Argo CD' }, - { icon: '', message: 'Create and update resources based on manifest' }, - ] - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false - } - } - } else if (element.status.includes(TIMELINE_STATUS.GIT_COMMIT)) { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time = element.statusTime - if (element.status === TIMELINE_STATUS.GIT_COMMIT_FAILED) { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = 'Failed' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'failed' - - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.isCollapsed = false - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED - deploymentData.deploymentStatusText = 'Failed' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.timelineStatus = element.statusDetail - } else { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success' - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon === '') { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable' - } - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - if (deploymentData.nonDeploymentError === '') { - deploymentData.nonDeploymentError = TIMELINE_STATUS.ARGOCD_SYNC - } - } else if ( - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time === '' && - deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS - ) { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [ - { icon: '', message: 'Waiting to be started by Argo CD' }, - { icon: '', message: 'Create and update resources based on manifest' }, - ] - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'inprogress' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'In progress' - } - } - } else if (element.status === TIMELINE_STATUS.DEPLOYMENT_INITIATED) { - deploymentData.deploymentStatusBreakdown.DEPLOYMENT_INITIATED.time = element.statusTime - if ( - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time === '' && - deploymentData.deploymentStatus === DEPLOYMENT_STATUS.INPROGRESS - ) { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'inprogress' - - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Waiting' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Waiting' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.kubeList = [ - { icon: '', message: 'Waiting to be started by Argo CD' }, - { icon: '', message: 'Create and update resources based on manifest' }, - ] - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = false - } - if (deploymentData.deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - if (deploymentData.deploymentStatusBreakdown.GIT_COMMIT.time === '') { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = '' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - deploymentData.nonDeploymentError = TIMELINE_STATUS.GIT_COMMIT - } else if (deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon !== 'failed') { - if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time === '') { - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unknown' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown' - } else if (deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon !== 'failed') { - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = 'Unknown' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unknown' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = ': Unknown' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unknown' - } - } else { - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.displaySubText = '' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.displaySubText = '' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'unreachable' - - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = '' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'unreachable' - - deploymentData.nonDeploymentError = TIMELINE_STATUS.GIT_COMMIT - } - } - } - } - } else if (!data?.timelines) { - // data when timelines is not available in case of the previously deployed app(deployment-status/timline api) ) - if (data?.wfrStatus === 'Healthy' || data?.wfrStatus === 'Succeeded') { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.SUCCEEDED - deploymentData.deploymentStatusText = 'Succeeded' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon = 'success' - deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.isCollapsed = true - deploymentData.deploymentStatusBreakdown.APP_HEALTH.isCollapsed = true - deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' - deploymentData.deploymentStatusBreakdown.GIT_COMMIT.icon = 'success' - } else if (data?.wfrStatus === 'Failed' || data?.wfrStatus === 'Degraded') { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.FAILED - deploymentData.deploymentStatusText = 'Failed' - deploymentData.deploymentStatusBreakdown.APP_HEALTH.displaySubText = 'Failed' - } else if (data?.wfrStatus === 'Progressing') { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.INPROGRESS - deploymentData.deploymentStatusText = 'In progress' - } else if (data?.wfrStatus === 'TimedOut') { - deploymentData.deploymentStatus = DEPLOYMENT_STATUS.TIMED_OUT - deploymentData.deploymentStatusText = 'Timed out' - } - } - return deploymentData -} - export function aggregateNodes(nodes: any[], podMetadata: PodMetadatum[]): AggregatedNodes { const podMetadataMap = mapByKey(podMetadata, 'name') // group nodes diff --git a/src/Shared/constants.tsx b/src/Shared/constants.tsx index ae2c2d9be..342bcbb39 100644 --- a/src/Shared/constants.tsx +++ b/src/Shared/constants.tsx @@ -80,28 +80,6 @@ export enum EnvironmentTypeEnum { nonProduction = 'Non-Production', } -export enum TIMELINE_STATUS { - DEPLOYMENT_INITIATED = 'DEPLOYMENT_INITIATED', - GIT_COMMIT = 'GIT_COMMIT', - GIT_COMMIT_FAILED = 'GIT_COMMIT_FAILED', - ARGOCD_SYNC = 'ARGOCD_SYNC', - ARGOCD_SYNC_FAILED = 'ARGOCD_SYNC_FAILED', - KUBECTL_APPLY = 'KUBECTL_APPLY', - KUBECTL_APPLY_STARTED = 'KUBECTL_APPLY_STARTED', - KUBECTL_APPLY_SYNCED = 'KUBECTL_APPLY_SYNCED', - HEALTHY = 'HEALTHY', - APP_HEALTH = 'APP_HEALTH', - DEPLOYMENT_FAILED = 'FAILED', - FETCH_TIMED_OUT = 'TIMED_OUT', - UNABLE_TO_FETCH_STATUS = 'UNABLE_TO_FETCH_STATUS', - DEGRADED = 'DEGRADED', - DEPLOYMENT_SUPERSEDED = 'DEPLOYMENT_SUPERSEDED', - ABORTED = 'ABORTED', - INPROGRESS = 'INPROGRESS', - HELM_PACKAGE_GENERATED = 'HELM_PACKAGE_GENERATED', - HELM_MANIFEST_PUSHED_TO_HELM_REPO = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO', - HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED', -} /** * Constants for NO MATCHING result */ diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 3d72140da..1e8e87a13 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -35,7 +35,7 @@ import { VulnerabilityType, } from '../Common' import { SelectPickerOptionType } from './Components' -import { BASE_CONFIGURATION_ENV_ID, EnvironmentTypeEnum, PatchOperationType } from './constants' +import { BASE_CONFIGURATION_ENV_ID, DEPLOYMENT_STATUS, EnvironmentTypeEnum, PatchOperationType } from './constants' export enum EnvType { CHART = 'helm_charts', @@ -1159,3 +1159,124 @@ export enum RegistryCredentialsType { USERNAME_PASSWORD = 'username_password', ANONYMOUS = 'anonymous', } + +export interface SyncStageResourceDetail { + id: number + cdWorkflowRunnerId: number + resourceGroup: string + resourceKind: string + resourceName: string + resourcePhase: string + resourceStatus: string + statusMessage: string +} + +export interface DeploymentStatusDetailsTimelineType { + id: number + cdWorkflowRunnerId: number + status: string + statusDetail: string + statusTime: string + resourceDetails?: SyncStageResourceDetail[] +} + +export interface DeploymentStatusDetailsType { + deploymentFinishedOn: string + deploymentStartedOn: string + triggeredBy: string + statusFetchCount: number + statusLastFetchedAt: string + timelines: DeploymentStatusDetailsTimelineType[] + wfrStatus?: string +} + +export enum TIMELINE_STATUS { + DEPLOYMENT_INITIATED = 'DEPLOYMENT_INITIATED', + GIT_COMMIT = 'GIT_COMMIT', + GIT_COMMIT_FAILED = 'GIT_COMMIT_FAILED', + ARGOCD_SYNC = 'ARGOCD_SYNC', + ARGOCD_SYNC_FAILED = 'ARGOCD_SYNC_FAILED', + KUBECTL_APPLY = 'KUBECTL_APPLY', + KUBECTL_APPLY_STARTED = 'KUBECTL_APPLY_STARTED', + KUBECTL_APPLY_SYNCED = 'KUBECTL_APPLY_SYNCED', + HEALTHY = 'HEALTHY', + APP_HEALTH = 'APP_HEALTH', + DEPLOYMENT_FAILED = 'FAILED', + FETCH_TIMED_OUT = 'TIMED_OUT', + UNABLE_TO_FETCH_STATUS = 'UNABLE_TO_FETCH_STATUS', + DEGRADED = 'DEGRADED', + DEPLOYMENT_SUPERSEDED = 'DEPLOYMENT_SUPERSEDED', + ABORTED = 'ABORTED', + INPROGRESS = 'INPROGRESS', + HELM_PACKAGE_GENERATED = 'HELM_PACKAGE_GENERATED', + HELM_PACKAGE_GENERATION_FAILED = 'HELM_PACKAGE_GENERATION_FAILED', + HELM_MANIFEST_PUSHED_TO_HELM_REPO = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO', + HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED', +} + +export type DeploymentStatusTimelineType = + | TIMELINE_STATUS.DEPLOYMENT_INITIATED + | TIMELINE_STATUS.GIT_COMMIT + | TIMELINE_STATUS.ARGOCD_SYNC + | TIMELINE_STATUS.KUBECTL_APPLY + | TIMELINE_STATUS.APP_HEALTH + | TIMELINE_STATUS.HELM_PACKAGE_GENERATED + | TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO + +export type DeploymentStatusBreakdownItemIconType = + | 'success' + | 'failed' + | 'unknown' + | 'inprogress' + | 'unreachable' + // Loading is for subSteps + | 'loading' + | 'disconnect' + | 'time_out' + | 'timed_out' + | '' + +export enum DeploymentPhaseType { + PRE_SYNC = 'PreSync', + SYNC = 'Sync', + POST_SYNC = 'PostSync', + SKIP = 'Skip', + SYNC_FAIL = 'SyncFail', +} + +export interface DeploymentStatusBreakdownItemType { + icon: DeploymentStatusBreakdownItemIconType + displayText: string + displaySubText: string + time: string + /** + * Shown in accordion details if type is TIMELINE_STATUS.KUBECTL_APPLY to display resource details + */ + resourceDetails?: SyncStageResourceDetail[] + isCollapsed?: boolean + /** + * Sub-Steps in accordion details in case type is TIMELINE_STATUS.KUBECTL_APPLY + */ + subSteps?: { icon: DeploymentStatusBreakdownItemIconType; message: string; phase?: DeploymentPhaseType }[] + /** + * To be shown in accordion details below heading tile + */ + timelineStatus?: string + showHelmManifest?: boolean +} + +export interface DeploymentStatusDetailsBreakdownDataType { + deploymentStatus: (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS] + deploymentTriggerTime: string + deploymentEndTime: string + /** + * Only required - isHelmManifestPushFailed === true then in error bar below heading tile + */ + deploymentError?: string + triggeredBy: string + /** + * Only required - isHelmManifestPushFailed === true then in error bar below heading tile + */ + lastFailedStatusType?: DeploymentStatusTimelineType | '' + deploymentStatusBreakdown: Partial> +} From 91015a1b82824ea7215dce189e6d60912dd3debc Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 01:47:28 +0530 Subject: [PATCH 07/32] feat: Update processKubeCTLApply to handle resource details from KUBECTL_APPLY_STARTED timeline --- .../Components/DeploymentStatusBreakdown/utils.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index d4fbc1f62..1b928aecf 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -130,10 +130,16 @@ const processKubeCTLApply = ( currentTableData: [{ icon: 'success', message: 'Started by Argo CD' }], } - if (element.resourceDetails) { + // Resource details are present in KUBECTL_APPLY_STARTED timeline alone + const resourceDetails = data?.timelines?.find( + (item) => item.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED, + )?.resourceDetails + + if (resourceDetails) { + // TODO: Confirm this logic since, can have duplication // Used to parse resource details base struct with current phase as last phase DEPLOYMENT_PHASES.forEach((phase) => { - element.resourceDetails.forEach((item) => { + resourceDetails.forEach((item) => { if (phase === item.resourcePhase) { tableData.currentPhase = phase tableData.currentTableData.push({ From 5a3485388e79c1e29eb84203d211d5b38f1a6773 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 10:17:09 +0530 Subject: [PATCH 08/32] feat: Refactor DeploymentStatusBreakdown component and update utility functions for improved clarity and functionality --- .../CICDHistory/DeploymentStatusBreakdown.tsx | 89 +++++++++---------- src/Shared/Components/CICDHistory/utils.tsx | 3 - .../DeploymentStatusBreakdown/utils.tsx | 2 +- src/Shared/types.ts | 1 - 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index 7176f4535..cce419847 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -15,12 +15,9 @@ */ import { Fragment } from 'react' -import { useRouteMatch } from 'react-router-dom' import { TIMELINE_STATUS } from '@Shared/types' -import { URLS } from '../../../Common' -import ErrorBar from '../Error/ErrorBar' import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow' import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType } from './types' @@ -31,7 +28,6 @@ const DeploymentStatusDetailBreakdown = ({ isVirtualEnvironment, appDetails, }: DeploymentStatusDetailBreakdownType) => { - const { url } = useRouteMatch() const isHelmManifestPushed = deploymentStatusDetailsBreakdownData.deploymentStatusBreakdown[ TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO @@ -44,56 +40,51 @@ const DeploymentStatusDetailBreakdown = ({ } return ( - <> - {!url.includes(`/${URLS.CD_DETAILS}`) && } -
- - {!( - isVirtualEnvironment && - deploymentStatusDetailsBreakdownData.deploymentStatusBreakdown[ - TIMELINE_STATUS.HELM_PACKAGE_GENERATED - ] - ) ? ( - <> - {( - [ - TIMELINE_STATUS.GIT_COMMIT, - TIMELINE_STATUS.ARGOCD_SYNC, - TIMELINE_STATUS.KUBECTL_APPLY, - ] as DeploymentStatusDetailRowType['type'][] - ).map((timelineStatus) => ( - - - - ))} +
+ + {!( + isVirtualEnvironment && + deploymentStatusDetailsBreakdownData.deploymentStatusBreakdown[TIMELINE_STATUS.HELM_PACKAGE_GENERATED] + ) ? ( + <> + {( + [ + TIMELINE_STATUS.GIT_COMMIT, + TIMELINE_STATUS.ARGOCD_SYNC, + TIMELINE_STATUS.KUBECTL_APPLY, + ] as DeploymentStatusDetailRowType['type'][] + ).map((timelineStatus) => ( + + + + ))} + + + ) : ( + <> + + {isHelmManifestPushed && ( - - ) : ( - <> - - {isHelmManifestPushed && ( - - )} - - )} -
- + )} + + )} +
) } diff --git a/src/Shared/Components/CICDHistory/utils.tsx b/src/Shared/Components/CICDHistory/utils.tsx index 0568796f8..2c2f05d82 100644 --- a/src/Shared/Components/CICDHistory/utils.tsx +++ b/src/Shared/Components/CICDHistory/utils.tsx @@ -368,9 +368,7 @@ export const renderDeploymentTimelineIcon = (iconState: DeploymentStatusBreakdow return case 'disconnect': return - case 'time_out': case 'timed_out': - // TODO: Test return default: return @@ -383,7 +381,6 @@ export const getDeploymentTimelineBGColorFromIcon = (icon: DeploymentStatusBreak return 'bcg-1 cg-7' case 'failed': case 'disconnect': - case 'time_out': case 'timed_out': return 'bcr-1 cr-5' case 'inprogress': diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 1b928aecf..3d45547b0 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -136,7 +136,7 @@ const processKubeCTLApply = ( )?.resourceDetails if (resourceDetails) { - // TODO: Confirm this logic since, can have duplication + // TODO: Confirm this logic since, can have duplication of resourcePhase in the resourceDetails // Used to parse resource details base struct with current phase as last phase DEPLOYMENT_PHASES.forEach((phase) => { resourceDetails.forEach((item) => { diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 1e8e87a13..c1c6a937f 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1232,7 +1232,6 @@ export type DeploymentStatusBreakdownItemIconType = // Loading is for subSteps | 'loading' | 'disconnect' - | 'time_out' | 'timed_out' | '' From 21c9226ee0bc844ad6d0f46ea569039fc887195f Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 10:28:47 +0530 Subject: [PATCH 09/32] feat: Update processKubeCTLApply to require data parameter and improve resource details handling --- .../Components/DeploymentStatusBreakdown/utils.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 3d45547b0..f05b17b4e 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -120,7 +120,7 @@ const processKubeCTLApply = ( timelineData: DeploymentStatusBreakdownItemType, element: DeploymentStatusDetailsTimelineType, deploymentStatus: DeploymentStatusDetailsBreakdownDataType['deploymentStatus'], - data?: DeploymentStatusDetailsType, + data: DeploymentStatusDetailsType, ) => { const tableData: { currentPhase: DeploymentPhaseType | '' @@ -131,15 +131,19 @@ const processKubeCTLApply = ( } // Resource details are present in KUBECTL_APPLY_STARTED timeline alone - const resourceDetails = data?.timelines?.find( + const resourceDetails = data.timelines.find( (item) => item.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED, )?.resourceDetails if (resourceDetails) { - // TODO: Confirm this logic since, can have duplication of resourcePhase in the resourceDetails // Used to parse resource details base struct with current phase as last phase DEPLOYMENT_PHASES.forEach((phase) => { + let breakPhase = false resourceDetails.forEach((item) => { + if (breakPhase) { + return + } + if (phase === item.resourcePhase) { tableData.currentPhase = phase tableData.currentTableData.push({ @@ -147,6 +151,7 @@ const processKubeCTLApply = ( phase, message: `${phase}: Create and update resources based on manifest`, }) + breakPhase = true } }) }) From a61697e51fbca35dbf6a9e9a7e1dad0b0aabb059 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 13:16:41 +0530 Subject: [PATCH 10/32] feat: Enhance AppStatusModal and service functions for improved deployment status handling and data processing --- .../AppStatusModal.component.tsx | 169 ++++++++++++++---- .../Components/AppStatusModal/service.ts | 62 ++++--- src/Shared/Components/AppStatusModal/types.ts | 36 ++-- src/Shared/Components/CICDHistory/service.tsx | 3 + src/Shared/types.ts | 1 + 5 files changed, 194 insertions(+), 77 deletions(-) diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 120d2f3c8..4521b9e68 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -8,28 +8,28 @@ import { GenericEmptyState } from '@Common/EmptyState' import { handleUTCTime, stopPropagation, useAsync } from '@Common/Helper' import { DeploymentAppTypes } from '@Common/Types' import { ComponentSizeType } from '@Shared/constants' +import { AppType } from '@Shared/types' import { APIResponseHandler } from '../APIResponseHandler' import { Button, ButtonComponentType, ButtonStyleType, ButtonVariantType } from '../Button' +import { PROGRESSING_DEPLOYMENT_STATUS } from '../DeploymentStatusBreakdown' import { Icon } from '../Icon' import { DeploymentStatus } from '../StatusComponent' import { AppStatusBody } from './AppStatusBody' import AppStatusModalTabList from './AppStatusModalTabList' -import { getAppDetails } from './service' +import { getAppDetails, getDeploymentStatusWithTimeline } from './service' import { AppStatusModalProps, AppStatusModalTabType } from './types' import { getEmptyViewImageFromHelmDeploymentStatus, getShowDeploymentStatusModal } from './utils' import './AppStatusModal.scss' -// TODO: Need to handleTabChange for appDetails view since polling is external const AppStatusModal = ({ titleSegments, handleClose, type, - isDeploymentTimelineLoading, appDetails: appDetailsProp, - deploymentStatusDetailsBreakdownData: deploymentStatusDetailsBreakdownDataProps, processVirtualEnvironmentDeploymentData, + handleUpdateDeploymentStatusDetailsBreakdownData, isConfigDriftEnabled, configDriftModal: ConfigDriftModal, appId, @@ -39,8 +39,8 @@ const AppStatusModal = ({ const [showConfigDriftModal, setShowConfigDriftModal] = useState(false) const [selectedTab, setSelectedTab] = useState(initialTab || null) - const abortControllerRef = useRef(new AbortController()) - const pollingTimeoutRef = useRef | null>(null) + const appDetailsAbortControllerRef = useRef(new AbortController()) + const appDetailsPollingTimeoutRef = useRef | null>(null) const getAppDetailsWrapper = async () => { const response = await abortPreviousRequests( @@ -48,12 +48,10 @@ const AppStatusModal = ({ getAppDetails({ appId, envId, - abortControllerRef, - deploymentStatusConfig: { showTimeline: false, processVirtualEnvironmentDeploymentData }, + abortControllerRef: appDetailsAbortControllerRef, }), - abortControllerRef, + appDetailsAbortControllerRef, ) - return response } @@ -65,35 +63,97 @@ const AppStatusModal = ({ setFetchedAppDetails, ] = useAsync(getAppDetailsWrapper, [appId, envId], type === 'release') - const handleExternalSync = async () => { - try { - pollingTimeoutRef.current = setTimeout( - async () => { + const appDetails = type === 'release' ? fetchedAppDetails : appDetailsProp + + const showDeploymentStatusModal = getShowDeploymentStatusModal({ type, appDetails }) + const deploymentStatusAbortControllerRef = useRef(new AbortController()) + const deploymentStatusPollingTimeoutRef = useRef | null>(null) + + const getDeploymentStatusWrapper = async () => { + const response = await abortPreviousRequests( + () => + getDeploymentStatusWithTimeline({ + abortControllerRef: deploymentStatusAbortControllerRef, + appId: + appDetails.appType === AppType.DEVTRON_HELM_CHART + ? appDetails.installedAppId + : appDetails.appId, + envId: appDetails.environmentId, + showTimeline: selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS, + virtualEnvironmentConfig: appDetails.isVirtualEnvironment + ? { + processVirtualEnvironmentDeploymentData, + wfrId: appDetails.resourceTree?.wfrId, + } + : null, + isHelmApp: appDetails.appType === AppType.DEVTRON_HELM_CHART, + }), + deploymentStatusAbortControllerRef, + ) + + handleUpdateDeploymentStatusDetailsBreakdownData?.(response) + + return response + } + + const [ + isDeploymentTimelineLoading, + deploymentStatusDetailsBreakdownData, + deploymentStatusDetailsBreakdownDataError, + reloadDeploymentStatusDetailsBreakdownData, + setDeploymentStatusDetailsBreakdownData, + ] = useAsync( + getDeploymentStatusWrapper, + [appId, envId, showDeploymentStatusModal, selectedTab], + !!showDeploymentStatusModal, + ) + + const handleAppDetailsExternalSync = async () => { + appDetailsPollingTimeoutRef.current = setTimeout( + async () => { + try { const response = await getAppDetailsWrapper() setFetchedAppDetails(response) - // eslint-disable-next-line @typescript-eslint/no-floating-promises - handleExternalSync() - }, - Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL) || 30000, - ) - } catch { - // Do nothing - } + } catch { + // Do nothing + } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + handleAppDetailsExternalSync() + }, + Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL) || 30000, + ) } - const appDetails = type === 'release' ? fetchedAppDetails?.appDetails : appDetailsProp - const deploymentStatusDetailsBreakdownData = - type === 'release' - ? fetchedAppDetails?.deploymentStatusDetailsBreakdownData - : deploymentStatusDetailsBreakdownDataProps + const handleDeploymentStatusExternalSync = async () => { + const isDeploymentInProgress = PROGRESSING_DEPLOYMENT_STATUS.includes( + deploymentStatusDetailsBreakdownData?.deploymentStatus, + ) + + const pollingIntervalFromFlag = Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL) || 30000 + + deploymentStatusPollingTimeoutRef.current = setTimeout( + async () => { + try { + const response = await getDeploymentStatusWrapper() + setDeploymentStatusDetailsBreakdownData(response) + } catch { + // Do nothing + } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + handleDeploymentStatusExternalSync() + }, + isDeploymentInProgress ? 10000 : pollingIntervalFromFlag, + ) + } const areInitialAppDetailsLoadingWithAbortedError = areInitialAppDetailsLoading || getIsRequestAborted(fetchedAppDetailsError) + const isDeploymentStatusLoadingWithAbortedError = + isDeploymentTimelineLoading || getIsRequestAborted(deploymentStatusDetailsBreakdownDataError) + const isTimelineRequiredAndLoading = - selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS && - appDetails?.deploymentAppType !== DeploymentAppTypes.HELM && - isDeploymentTimelineLoading + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS && isDeploymentStatusLoadingWithAbortedError // Adding useEffect to initiate timer for external sync and clear it on unmount useEffect(() => { @@ -101,20 +161,42 @@ const AppStatusModal = ({ !areInitialAppDetailsLoading && !fetchedAppDetailsError && fetchedAppDetails && - !pollingTimeoutRef.current + !appDetailsPollingTimeoutRef.current ) { // eslint-disable-next-line @typescript-eslint/no-floating-promises - handleExternalSync() + handleAppDetailsExternalSync() } }, [areInitialAppDetailsLoading, fetchedAppDetails, fetchedAppDetailsError]) + useEffect(() => { + if ( + !isDeploymentTimelineLoading && + !deploymentStatusDetailsBreakdownDataError && + deploymentStatusDetailsBreakdownData && + !deploymentStatusPollingTimeoutRef.current + ) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + handleDeploymentStatusExternalSync() + } + }, [isDeploymentTimelineLoading, deploymentStatusDetailsBreakdownData, deploymentStatusDetailsBreakdownDataError]) + + const handleClearDeploymentStatusTimeout = () => { + if (deploymentStatusPollingTimeoutRef.current) { + clearTimeout(deploymentStatusPollingTimeoutRef.current) + deploymentStatusPollingTimeoutRef.current = null + } + } + useEffect( () => () => { - if (pollingTimeoutRef.current) { - clearTimeout(pollingTimeoutRef.current) + if (appDetailsPollingTimeoutRef.current) { + clearTimeout(appDetailsPollingTimeoutRef.current) } - abortControllerRef.current.abort() + handleClearDeploymentStatusTimeout() + + appDetailsAbortControllerRef.current.abort() + deploymentStatusAbortControllerRef.current.abort() }, [], ) @@ -130,7 +212,8 @@ const AppStatusModal = ({ setShowConfigDriftModal(false) } - const handleSelectTab = (updatedTab: AppStatusModalTabType) => { + const handleSelectTab = async (updatedTab: AppStatusModalTabType) => { + handleClearDeploymentStatusTimeout() setSelectedTab(updatedTab) } @@ -224,6 +307,14 @@ const AppStatusModal = ({ ) } + const timelineError = + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS ? deploymentStatusDetailsBreakdownDataError : null + + const bodyErrorData = fetchedAppDetailsError || timelineError + const bodyErrorReload = fetchedAppDetailsError + ? reloadInitialAppDetails + : reloadDeploymentStatusDetailsBreakdownData + return (
{renderContent()} diff --git a/src/Shared/Components/AppStatusModal/service.ts b/src/Shared/Components/AppStatusModal/service.ts index d132a7115..e649a4eaa 100644 --- a/src/Shared/Components/AppStatusModal/service.ts +++ b/src/Shared/Components/AppStatusModal/service.ts @@ -9,49 +9,67 @@ import { } from '@Shared/types' import { processDeploymentStatusDetailsData } from '../DeploymentStatusBreakdown' -import { GetAppDetailsParamsType } from './types' +import { GetAppDetailsParamsType, GetDeploymentStatusWithTimelineParamsType } from './types' export const getAppDetails = async ({ appId, envId, abortControllerRef, - deploymentStatusConfig, -}: GetAppDetailsParamsType): Promise<{ - appDetails: AppDetails - deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType -}> => { +}: GetAppDetailsParamsType): Promise => { try { const queryParams = getUrlWithSearchParams('', { 'app-id': appId, 'env-id': envId, }) - const [appDetails, resourceTree, deploymentStatusDetails] = await Promise.all([ + const [appDetails, resourceTree] = await Promise.all([ get>(`${ROUTES.APP_DETAIL}/v2${queryParams}`, { abortControllerRef, }), get(`${ROUTES.APP_DETAIL}/resource-tree${queryParams}`, { abortControllerRef, }), - deploymentStatusConfig - ? get( - getUrlWithSearchParams(`${ROUTES.DEPLOYMENT_STATUS}/${appId}/${envId}`, { - showTimeline: deploymentStatusConfig.showTimeline, - }), - ) - : null, ]) return { - appDetails: { - ...(appDetails.result || ({} as AppDetails)), - resourceTree: resourceTree.result, - appType: AppType.DEVTRON_APP, - }, - deploymentStatusDetailsBreakdownData: appDetails.result?.isVirtualEnvironment - ? deploymentStatusConfig.processVirtualEnvironmentDeploymentData(deploymentStatusDetails.result) - : processDeploymentStatusDetailsData(deploymentStatusDetails.result), + ...(appDetails.result || ({} as AppDetails)), + resourceTree: resourceTree.result, + appType: AppType.DEVTRON_APP, + } + } catch (error) { + if (!getIsRequestAborted(error)) { + showError(error) } + throw error + } +} + +export const getDeploymentStatusWithTimeline = async ({ + abortControllerRef, + appId, + envId, + showTimeline, + virtualEnvironmentConfig, + isHelmApp, +}: GetDeploymentStatusWithTimelineParamsType): Promise => { + try { + const baseURL = isHelmApp ? ROUTES.HELM_DEPLOYMENT_STATUS_TIMELINE_INSTALLED_APP : ROUTES.DEPLOYMENT_STATUS + + const deploymentStatusDetailsResponse = await get( + getUrlWithSearchParams(`${baseURL}/${appId}/${envId}`, { + showTimeline, + ...(virtualEnvironmentConfig && { + wfrId: virtualEnvironmentConfig.wfrId, + }), + }), + { + abortControllerRef, + }, + ) + + return virtualEnvironmentConfig + ? virtualEnvironmentConfig.processVirtualEnvironmentDeploymentData(deploymentStatusDetailsResponse.result) + : processDeploymentStatusDetailsData(deploymentStatusDetailsResponse.result) } catch (error) { if (!getIsRequestAborted(error)) { showError(error) diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index 0e89ba529..5414c7e6e 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -18,35 +18,32 @@ export type AppStatusModalProps = { handleClose: () => void isConfigDriftEnabled: boolean configDriftModal: FunctionComponent + processVirtualEnvironmentDeploymentData: ( + data?: DeploymentStatusDetailsType, + ) => DeploymentStatusDetailsBreakdownDataType } & ( | { type: 'release' - processVirtualEnvironmentDeploymentData: ( - data?: DeploymentStatusDetailsType, - ) => DeploymentStatusDetailsBreakdownDataType appId: number envId: number appDetails?: never - deploymentStatusDetailsBreakdownData?: never initialTab?: never - isDeploymentTimelineLoading?: never + handleUpdateDeploymentStatusDetailsBreakdownData?: never } | { type: 'devtron-app' | 'other-apps' | 'stack-manager' appDetails: AppDetails - deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType | null initialTab: AppStatusModalTabType - isDeploymentTimelineLoading?: boolean - processVirtualEnvironmentDeploymentData?: never + handleUpdateDeploymentStatusDetailsBreakdownData: (data: DeploymentStatusDetailsBreakdownDataType) => void appId?: never envId?: never } ) -export interface AppStatusBodyProps - extends Required> { +export interface AppStatusBodyProps extends Required> { handleShowConfigDriftModal: () => void selectedTab: AppStatusModalTabType + deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType } export interface AppStatusContentProps @@ -71,17 +68,24 @@ export interface GetFilteredFlattenedNodesFromAppDetailsParamsType export interface GetAppDetailsParamsType extends Pick { appId: number envId: number +} + +export type GetDeploymentStatusWithTimelineParamsType = Pick & { /** - * If given would fetch deploymentConfig + * Incase of helm apps this is installed app id */ - deploymentStatusConfig: { - showTimeline: boolean + appId: number + envId: number + showTimeline: boolean + virtualEnvironmentConfig?: { processVirtualEnvironmentDeploymentData: AppStatusModalProps['processVirtualEnvironmentDeploymentData'] - } | null + wfrId: AppDetails['resourceTree']['wfrId'] + } + isHelmApp?: boolean } -export interface AppStatusModalTabListProps - extends Pick { +export interface AppStatusModalTabListProps extends Pick { handleSelectTab: (updatedTab: AppStatusModalTabType) => void selectedTab: AppStatusModalTabType + deploymentStatusDetailsBreakdownData: DeploymentStatusDetailsBreakdownDataType } diff --git a/src/Shared/Components/CICDHistory/service.tsx b/src/Shared/Components/CICDHistory/service.tsx index b28f2935c..582d524ee 100644 --- a/src/Shared/Components/CICDHistory/service.tsx +++ b/src/Shared/Components/CICDHistory/service.tsx @@ -81,6 +81,9 @@ export const cancelPrePostCdTrigger = (pipelineId, workflowRunner, isForceAbort: return trash(URL) } +/** + * @deprecated + */ export function getDeploymentStatusDetail( appId: string, envId: string, diff --git a/src/Shared/types.ts b/src/Shared/types.ts index c1c6a937f..6ada911ff 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -173,6 +173,7 @@ export interface ResourceTree { podMetadata: Array status: string resourcesSyncResult?: Record + wfrId?: number } export enum AppType { From 37674f8f9601bbe4c7b7d8ae11768e66e33e38ee Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 13:32:04 +0530 Subject: [PATCH 11/32] feat: Update AppStatusModal and AppStatusModalTabList to enhance tab selection logic and deployment status handling --- .../Components/AppStatusModal/AppStatusModal.component.tsx | 1 + src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 4521b9e68..a57669a6c 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -106,6 +106,7 @@ const AppStatusModal = ({ getDeploymentStatusWrapper, [appId, envId, showDeploymentStatusModal, selectedTab], !!showDeploymentStatusModal, + { resetOnChange: false }, ) const handleAppDetailsExternalSync = async () => { diff --git a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx index 30f08722b..62142ebb8 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx @@ -76,7 +76,7 @@ const AppStatusModalTabList = ({ // Could have achieved via onDataLoad but, have done this through useEffect to avoid abrupt shift in case some tabs went missing after polling useEffect(() => { - if (tabGroups.length) { + if (tabGroups.length && !selectedTab) { handleSelectTab(tabGroups[0]?.id as AppStatusModalTabType) } }, []) From fb7e30bb99aa0131d7cf5653703159c013200bd6 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 14:51:54 +0530 Subject: [PATCH 12/32] feat: Update AppStatusModal to conditionally show timeline based on deployment app type and remove outdated comment in processDeploymentStatusDetailsData --- .../Components/AppStatusModal/AppStatusModal.component.tsx | 4 +++- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index a57669a6c..a9eb23d9e 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -79,7 +79,9 @@ const AppStatusModal = ({ ? appDetails.installedAppId : appDetails.appId, envId: appDetails.environmentId, - showTimeline: selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS, + showTimeline: + selectedTab === AppStatusModalTabType.DEPLOYMENT_STATUS && + appDetails.deploymentAppType !== DeploymentAppTypes.HELM, virtualEnvironmentConfig: appDetails.isVirtualEnvironment ? { processVirtualEnvironmentDeploymentData, diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index f05b17b4e..66d4704ef 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -244,7 +244,6 @@ export const processDeploymentStatusDetailsData = ( // After initial processing will mark all unavailable timelines [present before last invalid state] as success PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.forEach((timelineStatusType, index) => { - // TODO: Confirm for findRight approach is fine? since, for e.g, i believe apply start should be before apply synced const element = findRight(data.timelines, getPredicate(timelineStatusType)) const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] From 65b5aa1dcfd7f8f606154c6eb0877546427b7320 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 15:49:27 +0530 Subject: [PATCH 13/32] feat: Enhance AppStatusModal and related components to support environment ID and improve deployment status handling --- .../AppStatusModal/AppStatusBody.tsx | 9 ++- .../AppStatusModal.component.tsx | 3 +- .../Components/AppStatusModal/service.ts | 78 ++++++++----------- .../Components/AppStatusModal/utils.tsx | 33 ++++---- .../CICDHistory/DeploymentDetailSteps.tsx | 12 +-- .../DeploymentStatusBreakdown/utils.tsx | 3 +- 6 files changed, 67 insertions(+), 71 deletions(-) diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 4678f8d0e..a873ec74d 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -40,7 +40,8 @@ const StatusHeadingContainer = ({ children, type, appId, -}: PropsWithChildren> & { appId: number }) => ( + envId, +}: PropsWithChildren> & { appId: number; envId?: number }) => (
{children} {type === 'release' ? ( @@ -52,7 +53,7 @@ const StatusHeadingContainer = ({ endIcon={} text="Visit app" linkProps={{ - to: getAppDetailsURL(appId), + to: getAppDetailsURL(appId, envId), target: '_blank', rel: 'noopener noreferrer', }} @@ -82,7 +83,7 @@ export const AppStatusBody = ({ id: `app-status-${1}`, heading: type !== 'stack-manager' ? 'Application Status' : 'Status', value: ( - + {appStatus ? : '--'} ), @@ -116,7 +117,7 @@ export const AppStatusBody = ({ id: `deployment-status-${1}`, heading: 'Deployment Status', value: ( - + {deploymentStatusDetailsBreakdownData?.deploymentStatus ? ( } subTitle={`Triggered at ${handleUTCTime(deploymentStatusDetailsBreakdownData.deploymentTriggerTime)} by ${deploymentStatusDetailsBreakdownData.triggeredBy}`} + imageType={ImageType.Large} /> ) } diff --git a/src/Shared/Components/AppStatusModal/service.ts b/src/Shared/Components/AppStatusModal/service.ts index e649a4eaa..6d74ee4bb 100644 --- a/src/Shared/Components/AppStatusModal/service.ts +++ b/src/Shared/Components/AppStatusModal/service.ts @@ -1,6 +1,6 @@ -import { get, getIsRequestAborted } from '@Common/API' +import { get } from '@Common/API' import { ROUTES } from '@Common/Constants' -import { getUrlWithSearchParams, showError } from '@Common/Helper' +import { getUrlWithSearchParams } from '@Common/Helper' import { AppDetails, AppType, @@ -16,31 +16,24 @@ export const getAppDetails = async ({ envId, abortControllerRef, }: GetAppDetailsParamsType): Promise => { - try { - const queryParams = getUrlWithSearchParams('', { - 'app-id': appId, - 'env-id': envId, - }) + const queryParams = getUrlWithSearchParams('', { + 'app-id': appId, + 'env-id': envId, + }) - const [appDetails, resourceTree] = await Promise.all([ - get>(`${ROUTES.APP_DETAIL}/v2${queryParams}`, { - abortControllerRef, - }), - get(`${ROUTES.APP_DETAIL}/resource-tree${queryParams}`, { - abortControllerRef, - }), - ]) + const [appDetails, resourceTree] = await Promise.all([ + get>(`${ROUTES.APP_DETAIL}/v2${queryParams}`, { + abortControllerRef, + }), + get(`${ROUTES.APP_DETAIL}/resource-tree${queryParams}`, { + abortControllerRef, + }), + ]) - return { - ...(appDetails.result || ({} as AppDetails)), - resourceTree: resourceTree.result, - appType: AppType.DEVTRON_APP, - } - } catch (error) { - if (!getIsRequestAborted(error)) { - showError(error) - } - throw error + return { + ...(appDetails.result || ({} as AppDetails)), + resourceTree: resourceTree.result, + appType: AppType.DEVTRON_APP, } } @@ -52,28 +45,21 @@ export const getDeploymentStatusWithTimeline = async ({ virtualEnvironmentConfig, isHelmApp, }: GetDeploymentStatusWithTimelineParamsType): Promise => { - try { - const baseURL = isHelmApp ? ROUTES.HELM_DEPLOYMENT_STATUS_TIMELINE_INSTALLED_APP : ROUTES.DEPLOYMENT_STATUS + const baseURL = isHelmApp ? ROUTES.HELM_DEPLOYMENT_STATUS_TIMELINE_INSTALLED_APP : ROUTES.DEPLOYMENT_STATUS - const deploymentStatusDetailsResponse = await get( - getUrlWithSearchParams(`${baseURL}/${appId}/${envId}`, { - showTimeline, - ...(virtualEnvironmentConfig && { - wfrId: virtualEnvironmentConfig.wfrId, - }), + const deploymentStatusDetailsResponse = await get( + getUrlWithSearchParams(`${baseURL}/${appId}/${envId}`, { + showTimeline, + ...(virtualEnvironmentConfig && { + wfrId: virtualEnvironmentConfig.wfrId, }), - { - abortControllerRef, - }, - ) + }), + { + abortControllerRef, + }, + ) - return virtualEnvironmentConfig - ? virtualEnvironmentConfig.processVirtualEnvironmentDeploymentData(deploymentStatusDetailsResponse.result) - : processDeploymentStatusDetailsData(deploymentStatusDetailsResponse.result) - } catch (error) { - if (!getIsRequestAborted(error)) { - showError(error) - } - throw error - } + return virtualEnvironmentConfig + ? virtualEnvironmentConfig.processVirtualEnvironmentDeploymentData(deploymentStatusDetailsResponse.result) + : processDeploymentStatusDetailsData(deploymentStatusDetailsResponse.result) } diff --git a/src/Shared/Components/AppStatusModal/utils.tsx b/src/Shared/Components/AppStatusModal/utils.tsx index d37395764..d02794500 100644 --- a/src/Shared/Components/AppStatusModal/utils.tsx +++ b/src/Shared/Components/AppStatusModal/utils.tsx @@ -5,10 +5,15 @@ import NoDeploymentStatusImage from '@Images/no-artifact.webp' import { DeploymentAppTypes, GenericEmptyStateType } from '@Common/Types' import { DEPLOYMENT_STATUS } from '@Shared/constants' import { aggregateNodes } from '@Shared/Helpers' -import { AppDetails, AppType, Node } from '@Shared/types' +import { AppDetails, AppType, DeploymentStatusDetailsBreakdownDataType, Node } from '@Shared/types' import { ReleaseMode } from '@Pages/index' import { AggregatedNodes, STATUS_SORTING_ORDER } from '../CICDHistory' +import { + FAILED_DEPLOYMENT_STATUS, + PROGRESSING_DEPLOYMENT_STATUS, + SUCCESSFUL_DEPLOYMENT_STATUS, +} from '../DeploymentStatusBreakdown' import { AppStatusModalProps, GetFilteredFlattenedNodesFromAppDetailsParamsType as GetFlattenedNodesFromAppDetailsParamsType, @@ -105,20 +110,20 @@ export const getShowDeploymentStatusModal = ({ return true } -export const getEmptyViewImageFromHelmDeploymentStatus = (status: string): GenericEmptyStateType['image'] => { - switch (status?.toLowerCase()) { - case DEPLOYMENT_STATUS.STARTING: - case DEPLOYMENT_STATUS.PROGRESSING: - case DEPLOYMENT_STATUS.INITIATING: - return ICManOnRocket - - case DEPLOYMENT_STATUS.SUCCEEDED: - return ICCelebration +export const getEmptyViewImageFromHelmDeploymentStatus = ( + status: DeploymentStatusDetailsBreakdownDataType['deploymentStatus'], +): GenericEmptyStateType['image'] => { + if (PROGRESSING_DEPLOYMENT_STATUS.includes(status)) { + return ICManOnRocket + } - case DEPLOYMENT_STATUS.FAILED: - return ICPageNotFound + if (SUCCESSFUL_DEPLOYMENT_STATUS.includes(status)) { + return ICCelebration + } - default: - return NoDeploymentStatusImage + if (FAILED_DEPLOYMENT_STATUS.includes(status)) { + return ICPageNotFound } + + return NoDeploymentStatusImage } diff --git a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx index 697f6cfd5..86fbf84cb 100644 --- a/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx @@ -153,11 +153,13 @@ const DeploymentDetailSteps = ({ {renderDeploymentApprovalInfo && getIsApprovalPolicyConfigured(userApprovalMetadata?.approvalConfigData) && renderDeploymentApprovalInfo(userApprovalMetadata)} - +
+ +
) diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 66d4704ef..5b4219c97 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -245,6 +245,7 @@ export const processDeploymentStatusDetailsData = ( // After initial processing will mark all unavailable timelines [present before last invalid state] as success PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.forEach((timelineStatusType, index) => { const element = findRight(data.timelines, getPredicate(timelineStatusType)) + const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] if (!element) { @@ -290,6 +291,7 @@ export const processDeploymentStatusDetailsData = ( case TIMELINE_STATUS.ARGOCD_SYNC: timelineData.time = element.statusTime timelineData.icon = 'success' + timelineData.displaySubText = '' // These are singular events so either their success will come or failure if ( @@ -337,7 +339,6 @@ export const processDeploymentStatusDetailsData = ( break } - // TODO: Should we add in progress check here? if (timelineData.icon === 'success' && index !== PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.length - 1) { // Moving the next timeline to inprogress const nextTimelineStatus = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[index + 1] From 68aba96c9b87ea857598433d3eef1ea29724b22c Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 17:30:00 +0530 Subject: [PATCH 14/32] refactor: Update timelineStatus type to ReactNode in DeploymentStatusBreakdownItemType --- .../CICDHistory/DeploymentStatusDetailRow.tsx | 25 ++----------------- src/Shared/types.ts | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx index 4146b88be..e656fcbda 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx @@ -24,7 +24,7 @@ import { AppType, TIMELINE_STATUS } from '@Shared/types' import { ReactComponent as DropDownIcon } from '../../../Assets/Icon/ic-chevron-down.svg' import { DATE_TIME_FORMATS, showError } from '../../../Common' -import { ComponentSizeType, DEPLOYMENT_STATUS, statusIcon } from '../../constants' +import { ComponentSizeType, DEPLOYMENT_STATUS } from '../../constants' import { AppStatusContent } from '../AppStatusModal' import { Button, ButtonStyleType, ButtonVariantType } from '../Button' import { APP_HEALTH_DROP_DOWN_LIST, MANIFEST_STATUS_HEADERS, TERMINAL_STATUS_MAP } from './constants' @@ -47,10 +47,6 @@ export const DeploymentStatusDetailRow = ({ const statusBreakDownType = deploymentDetailedData.deploymentStatusBreakdown[type] const [isCollapsed, setIsCollapsed] = useState(statusBreakDownType.isCollapsed) - const isHelmManifestPushFailed = - type === TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO && - deploymentDetailedData.deploymentStatus === statusIcon.failed - useEffect(() => { setIsCollapsed(statusBreakDownType.isCollapsed) }, [statusBreakDownType.isCollapsed]) @@ -132,22 +128,6 @@ export const DeploymentStatusDetailRow = ({ ) } - const renderErrorInfoBar = () => { - if (deploymentDetailedData.lastFailedStatusType !== TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO) { - return null - } - - return ( -
- {deploymentDetailedData.deploymentError} -
    -
  1. Ensure provided repository path is valid
  2. -
  3. Check if credentials provided for OCI registry are valid and have PUSH permission
  4. -
-
- ) - } - const isAccordion = statusBreakDownType.subSteps?.length || (type === TIMELINE_STATUS.APP_HEALTH && APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) || @@ -198,7 +178,7 @@ export const DeploymentStatusDetailRow = ({ <>
{renderDeploymentTimelineIcon(statusBreakDownType.icon)} @@ -247,7 +227,6 @@ export const DeploymentStatusDetailRow = ({ /> )}
- {isHelmManifestPushFailed && renderErrorInfoBar()}
{renderAccordionDetails()} diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 67e8f5527..7e559e365 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1265,7 +1265,7 @@ export interface DeploymentStatusBreakdownItemType { /** * To be shown in accordion details below heading tile */ - timelineStatus?: string + timelineStatus?: ReactNode showHelmManifest?: boolean } From f97f6ca3a51cc20b5cc82aec7005201340200642 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 17:30:59 +0530 Subject: [PATCH 15/32] refactor: Remove unused deploymentError field from DeploymentStatusDetailsBreakdownDataType --- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 1 - src/Shared/types.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 5b4219c97..1695ba5a5 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -39,7 +39,6 @@ const getDefaultDeploymentStatusTimeline = ( deploymentStatus: WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP[data?.wfrStatus] || DEPLOYMENT_STATUS.INPROGRESS, deploymentTriggerTime: data?.deploymentStartedOn || '', deploymentEndTime: data?.deploymentFinishedOn || '', - deploymentError: '', triggeredBy: data?.triggeredBy || '', lastFailedStatusType: '', deploymentStatusBreakdown: { diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 7e559e365..ea4741980 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1273,10 +1273,6 @@ export interface DeploymentStatusDetailsBreakdownDataType { deploymentStatus: (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS] deploymentTriggerTime: string deploymentEndTime: string - /** - * Only required - isHelmManifestPushFailed === true then in error bar below heading tile - */ - deploymentError?: string triggeredBy: string /** * Only required - isHelmManifestPushFailed === true then in error bar below heading tile From 755c127c9b063e6af071601f8870165c9f10fb75 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 17:51:10 +0530 Subject: [PATCH 16/32] refactor: Remove unused lastFailedStatusType from DeploymentStatusDetailsBreakdownDataType and update related types --- src/Shared/Components/CICDHistory/types.tsx | 8 -------- .../Components/DeploymentStatusBreakdown/utils.tsx | 1 - src/Shared/types.ts | 13 ++++--------- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Shared/Components/CICDHistory/types.tsx b/src/Shared/Components/CICDHistory/types.tsx index 9f419f3bb..b5e9dff11 100644 --- a/src/Shared/Components/CICDHistory/types.tsx +++ b/src/Shared/Components/CICDHistory/types.tsx @@ -527,14 +527,6 @@ export interface DeploymentStatusDetailRowType extends Pick { status: string statusDetail: string statusTime: string @@ -1191,7 +1190,7 @@ export interface DeploymentStatusDetailsType { statusFetchCount: number statusLastFetchedAt: string timelines: DeploymentStatusDetailsTimelineType[] - wfrStatus?: string + wfrStatus?: WorkflowRunnerStatusDTO isDeploymentWithoutApproval: boolean } @@ -1274,10 +1273,6 @@ export interface DeploymentStatusDetailsBreakdownDataType { deploymentTriggerTime: string deploymentEndTime: string triggeredBy: string - /** - * Only required - isHelmManifestPushFailed === true then in error bar below heading tile - */ - lastFailedStatusType?: DeploymentStatusTimelineType | '' deploymentStatusBreakdown: Partial> } From 50e18013dcf84f2b346d27b57eb449302fbdfdd5 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 18:50:30 +0530 Subject: [PATCH 17/32] refactor: Simplify component structure and improve state management in DeploymentStatus components --- .../CICDHistory/DeploymentStatusBreakdown.tsx | 10 ++-- .../CICDHistory/DeploymentStatusDetailRow.tsx | 7 ++- .../DeploymentStatusBreakdown/constants.ts | 12 ++--- .../DeploymentStatusBreakdown/types.ts | 2 +- .../DeploymentStatusBreakdown/utils.tsx | 47 +++++++------------ .../Components/StatusComponent/utils.ts | 3 ++ src/Shared/types.ts | 38 +++++++-------- 7 files changed, 54 insertions(+), 65 deletions(-) diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index cce419847..019b98e72 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -14,8 +14,6 @@ * limitations under the License. */ -import { Fragment } from 'react' - import { TIMELINE_STATUS } from '@Shared/types' import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow' @@ -57,9 +55,11 @@ const DeploymentStatusDetailBreakdown = ({ TIMELINE_STATUS.KUBECTL_APPLY, ] as DeploymentStatusDetailRowType['type'][] ).map((timelineStatus) => ( - - - + ))} { - setIsCollapsed(!isCollapsed) + setIsCollapsed((prevState) => !prevState) } const renderDetailedData = () => { @@ -79,7 +79,6 @@ export const DeploymentStatusDetailRow = ({ return (
- {/* TODO: Can be statusBreakDownType */} {statusBreakDownType.subSteps?.map((items, index) => ( // eslint-disable-next-line react/no-array-index-key
@@ -101,7 +100,7 @@ export const DeploymentStatusDetailRow = ({
{statusBreakDownType.resourceDetails.map((nodeDetails) => (
{nodeDetails.resourceKind}
@@ -129,7 +128,7 @@ export const DeploymentStatusDetailRow = ({ } const isAccordion = - statusBreakDownType.subSteps?.length || + !!statusBreakDownType.subSteps?.length || (type === TIMELINE_STATUS.APP_HEALTH && APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) || ((type === TIMELINE_STATUS.GIT_COMMIT || type === TIMELINE_STATUS.ARGOCD_SYNC) && statusBreakDownType.icon === 'failed') diff --git a/src/Shared/Components/DeploymentStatusBreakdown/constants.ts b/src/Shared/Components/DeploymentStatusBreakdown/constants.ts index 737065370..3b961cb7c 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/constants.ts +++ b/src/Shared/Components/DeploymentStatusBreakdown/constants.ts @@ -3,12 +3,13 @@ import { DeploymentPhaseType, DeploymentStatusTimelineType, TIMELINE_STATUS } fr import { WorkflowRunnerStatusDTO } from './types' -export const DEPLOYMENT_STATUS_TEXT_MAP: Record<(typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS], string> = { +export const DEPLOYMENT_STATUS_TEXT_MAP: Readonly< + Record<(typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS], string> +> = { [DEPLOYMENT_STATUS.SUCCEEDED]: 'Succeeded', [DEPLOYMENT_STATUS.HEALTHY]: 'Healthy', [DEPLOYMENT_STATUS.FAILED]: 'Failed', [DEPLOYMENT_STATUS.TIMED_OUT]: 'Timed out', - // TODO: Add icons [DEPLOYMENT_STATUS.UNABLE_TO_FETCH]: 'Unable to fetch status', [DEPLOYMENT_STATUS.INPROGRESS]: 'In progress', [DEPLOYMENT_STATUS.PROGRESSING]: 'Progressing', @@ -21,9 +22,8 @@ export const DEPLOYMENT_STATUS_TEXT_MAP: Record<(typeof DEPLOYMENT_STATUS)[keyof } // Might be more but as per BE its only these for now -export const WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP: Record< - WorkflowRunnerStatusDTO, - (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS] +export const WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP: Readonly< + Record > = { [WorkflowRunnerStatusDTO.ABORTED]: DEPLOYMENT_STATUS.FAILED, [WorkflowRunnerStatusDTO.FAILED]: DEPLOYMENT_STATUS.FAILED, @@ -43,7 +43,7 @@ export const WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP: Record< [WorkflowRunnerStatusDTO.QUEUED]: DEPLOYMENT_STATUS.QUEUED, } -export const PROGRESSING_DEPLOYMENT_STATUS: (typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS][] = [ +export const PROGRESSING_DEPLOYMENT_STATUS: Readonly<(typeof DEPLOYMENT_STATUS)[keyof typeof DEPLOYMENT_STATUS][]> = [ DEPLOYMENT_STATUS.INPROGRESS, DEPLOYMENT_STATUS.PROGRESSING, DEPLOYMENT_STATUS.STARTING, diff --git a/src/Shared/Components/DeploymentStatusBreakdown/types.ts b/src/Shared/Components/DeploymentStatusBreakdown/types.ts index 8176a4a0e..811f83c19 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/types.ts +++ b/src/Shared/Components/DeploymentStatusBreakdown/types.ts @@ -15,7 +15,7 @@ export enum WorkflowRunnerStatusDTO { STARTING = 'Starting', QUEUED = 'Queued', INITIATING = 'Initiating', - // Not found on BE but for Backward compatibility + // Not found on BE but added for Backward compatibility HEALTHY = 'Healthy', DEGRADED = 'Degraded', } diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 0206efcdb..b2e211b1e 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -86,7 +86,7 @@ const getPredicate = case TIMELINE_STATUS.APP_HEALTH: return [TIMELINE_STATUS.HEALTHY, TIMELINE_STATUS.DEGRADED, TIMELINE_STATUS.DEPLOYMENT_FAILED].includes( - timelineItem.status as TIMELINE_STATUS, + timelineItem.status, ) default: @@ -136,22 +136,15 @@ const processKubeCTLApply = ( if (resourceDetails) { // Used to parse resource details base struct with current phase as last phase DEPLOYMENT_PHASES.forEach((phase) => { - let breakPhase = false - resourceDetails.forEach((item) => { - if (breakPhase) { - return - } - - if (phase === item.resourcePhase) { - tableData.currentPhase = phase - tableData.currentTableData.push({ - icon: 'success', - phase, - message: `${phase}: Create and update resources based on manifest`, - }) - breakPhase = true - } - }) + const resourceWithSamePhase = resourceDetails.find((item) => item.resourcePhase === phase) + if (resourceWithSamePhase) { + tableData.currentPhase = phase + tableData.currentTableData.push({ + icon: 'success', + phase, + message: `${phase}: Create and update resources based on manifest`, + }) + } }) } @@ -231,16 +224,15 @@ export const processDeploymentStatusDetailsData = ( return deploymentData } - // Would move for each timeline iteratively and if timeline is in terminal state then early return - // If timeline is in non-terminal state then we mark it as waiting - if (!data?.timelines?.length) { + if (!data.timelines.length) { return deploymentData } const isProgressing = PROGRESSING_DEPLOYMENT_STATUS.includes(deploymentStatus) - const isArgoCDAvailable = data.timelines.some((timeline) => timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC)) + const isArgoCDSyncAvailable = data.timelines.some((timeline) => + timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC), + ) - // After initial processing will mark all unavailable timelines [present before last invalid state] as success PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.forEach((timelineStatusType, index) => { const element = findRight(data.timelines, getPredicate(timelineStatusType)) @@ -292,11 +284,7 @@ export const processDeploymentStatusDetailsData = ( timelineData.displaySubText = '' // These are singular events so either their success will come or failure - if ( - [TIMELINE_STATUS.GIT_COMMIT_FAILED, TIMELINE_STATUS.ARGOCD_SYNC_FAILED].includes( - element.status as TIMELINE_STATUS, - ) - ) { + if ([TIMELINE_STATUS.GIT_COMMIT_FAILED, TIMELINE_STATUS.ARGOCD_SYNC_FAILED].includes(element.status)) { timelineData.displaySubText = 'Failed' timelineData.icon = 'failed' timelineData.isCollapsed = false @@ -307,7 +295,7 @@ export const processDeploymentStatusDetailsData = ( break case TIMELINE_STATUS.KUBECTL_APPLY: { - if (!isArgoCDAvailable) { + if (!isArgoCDSyncAvailable) { deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime @@ -337,8 +325,8 @@ export const processDeploymentStatusDetailsData = ( break } + // Moving the next timeline to inprogress if (timelineData.icon === 'success' && index !== PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.length - 1) { - // Moving the next timeline to inprogress const nextTimelineStatus = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[index + 1] const nextTimeline = deploymentData.deploymentStatusBreakdown[nextTimelineStatus] @@ -353,7 +341,6 @@ export const processDeploymentStatusDetailsData = ( const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] if (timelineData.icon === 'inprogress' || timelineData.icon === 'success') { - // If the timeline is in progress or success then we will mark all the previous steps as success for (let j = i - 1; j >= 0; j -= 1) { const prevTimelineStatusType = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[j] const prevTimelineData = deploymentData.deploymentStatusBreakdown[prevTimelineStatusType] diff --git a/src/Shared/Components/StatusComponent/utils.ts b/src/Shared/Components/StatusComponent/utils.ts index 29773051b..20972b952 100644 --- a/src/Shared/Components/StatusComponent/utils.ts +++ b/src/Shared/Components/StatusComponent/utils.ts @@ -70,6 +70,8 @@ export const getIconName = (status: string, showAnimatedIcon: boolean): IconName case 'timedout': case 'timed_out': return 'ic-timeout-dash' + case 'unable_to_fetch': + return 'ic-disconnect' default: return null } @@ -90,6 +92,7 @@ export const getIconColor = (status: string): IconsProps['color'] => { case 'request_accepted': case 'starting': return 'O500' + case 'unable_to_fetch': case 'timedout': case 'timed_out': return 'R500' diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 07e0527cf..a0fed9ca1 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1175,25 +1175,6 @@ export interface SyncStageResourceDetail { statusMessage: string } -export interface DeploymentStatusDetailsTimelineType - extends Pick { - status: string - statusDetail: string - statusTime: string - resourceDetails?: SyncStageResourceDetail[] -} - -export interface DeploymentStatusDetailsType { - deploymentFinishedOn: string - deploymentStartedOn: string - triggeredBy: string - statusFetchCount: number - statusLastFetchedAt: string - timelines: DeploymentStatusDetailsTimelineType[] - wfrStatus?: WorkflowRunnerStatusDTO - isDeploymentWithoutApproval: boolean -} - export enum TIMELINE_STATUS { DEPLOYMENT_INITIATED = 'DEPLOYMENT_INITIATED', GIT_COMMIT = 'GIT_COMMIT', @@ -1218,6 +1199,25 @@ export enum TIMELINE_STATUS { HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED = 'HELM_MANIFEST_PUSHED_TO_HELM_REPO_FAILED', } +export interface DeploymentStatusDetailsTimelineType + extends Pick { + status: TIMELINE_STATUS + statusDetail: string + statusTime: string + resourceDetails?: SyncStageResourceDetail[] +} + +export interface DeploymentStatusDetailsType { + deploymentFinishedOn: string + deploymentStartedOn: string + triggeredBy: string + statusFetchCount: number + statusLastFetchedAt: string + timelines: DeploymentStatusDetailsTimelineType[] + wfrStatus?: WorkflowRunnerStatusDTO + isDeploymentWithoutApproval: boolean +} + export type DeploymentStatusTimelineType = | TIMELINE_STATUS.DEPLOYMENT_INITIATED | TIMELINE_STATUS.GIT_COMMIT From 071fff1450e9b553a60635f9879868aa1b7e7fa1 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 13 May 2025 18:58:59 +0530 Subject: [PATCH 18/32] refactor: Rename actionButton to actionItem in StatusHeadingContainer and update related IDs for consistency --- .../Components/AppStatusModal/AppStatusBody.tsx | 16 ++++++++-------- .../AppStatusModal/AppStatusModalTabList.tsx | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 7e392385c..82b41a30e 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -43,17 +43,17 @@ const StatusHeadingContainer = ({ type, appId, envId, - actionButton, + actionItem, }: PropsWithChildren> & { appId: number envId?: number - actionButton?: ReactNode + actionItem?: ReactNode }) => (
{children}
- {actionButton} + {actionItem} {type === 'release' ? (
) diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss index f4f1158bf..0c33b2fca 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.scss @@ -56,4 +56,8 @@ border-radius: 0 0 4px 4px; border-top: 0; } -} \ No newline at end of file +} + +.deployment-approval-container + .deployment-status-breakdown-container { + padding-top: 0px; +} diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index 019b98e72..4a26a54c9 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -25,6 +25,7 @@ const DeploymentStatusDetailBreakdown = ({ deploymentStatusDetailsBreakdownData, isVirtualEnvironment, appDetails, + rootClassName = '', }: DeploymentStatusDetailBreakdownType) => { const isHelmManifestPushed = deploymentStatusDetailsBreakdownData.deploymentStatusBreakdown[ @@ -38,7 +39,10 @@ const DeploymentStatusDetailBreakdown = ({ } return ( -
+
{statusBreakDownType.displaySubText && ( - + {statusBreakDownType.displaySubText} )} diff --git a/src/Shared/Components/CICDHistory/types.tsx b/src/Shared/Components/CICDHistory/types.tsx index b5e9dff11..cc102c0a0 100644 --- a/src/Shared/Components/CICDHistory/types.tsx +++ b/src/Shared/Components/CICDHistory/types.tsx @@ -519,6 +519,7 @@ export interface DeploymentStatusDetailBreakdownType { * Won't be available if coming directly to deployment history from url */ appDetails: AppDetails | null + rootClassName?: string } export interface DeploymentStatusDetailRowType extends Pick { From fd34959936d94f6f040512b6ac959e5e3b44ed0f Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 14 May 2025 19:00:42 +0530 Subject: [PATCH 22/32] fix: review comments --- src/Common/Helper.tsx | 2 +- .../AppStatusModal/AppStatusBody.tsx | 24 ++++---------- .../AppStatusModal.component.tsx | 22 ++++++++++--- .../AppStatusModal/AppStatusModalTabList.tsx | 2 +- src/Shared/Components/AppStatusModal/types.ts | 8 ++++- .../Components/AppStatusModal/utils.tsx | 25 ++++----------- .../CICDHistory/DeploymentStatusDetailRow.tsx | 14 ++++---- .../DeploymentStatusBreakdown/types.ts | 2 +- .../DeploymentStatusBreakdown/utils.tsx | 32 +++++++++++++++---- .../Components/TabGroup/TabGroup.types.ts | 26 +++++++++++---- src/Shared/constants.tsx | 3 ++ 11 files changed, 97 insertions(+), 63 deletions(-) diff --git a/src/Common/Helper.tsx b/src/Common/Helper.tsx index f81054c9d..d060a1042 100644 --- a/src/Common/Helper.tsx +++ b/src/Common/Helper.tsx @@ -1124,7 +1124,7 @@ const getAppTypeCategory = (appType: AppType) => { export const getAIAnalyticsEvents = (context: string, appType?: AppType) => `AI_${appType ? `${getAppTypeCategory(appType)}_` : ''}${context}` -export const findRight = (arr: T[], predicate: (item: T) => boolean): T => { +export const findRight = (arr: T[], predicate: (item: T) => boolean): T | null => { for (let i = arr.length - 1; i >= 0; i--) { if (predicate(arr[i])) { return arr[i] diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 8375ad57d..09dba91d2 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -1,4 +1,4 @@ -import { ComponentProps, PropsWithChildren, ReactNode } from 'react' +import { ComponentProps, ReactNode } from 'react' import { getAIAnalyticsEvents } from '@Common/Helper' import { Tooltip } from '@Common/Tooltip' @@ -13,7 +13,7 @@ import { ShowMoreText } from '../ShowMoreText' import { AppStatus, DeploymentStatus, StatusType } from '../StatusComponent' import AppStatusContent from './AppStatusContent' import { APP_STATUS_CUSTOM_MESSAGES } from './constants' -import { AppStatusBodyProps, AppStatusModalTabType } from './types' +import { AppStatusBodyProps, AppStatusModalTabType, StatusHeadingContainerProps } from './types' import { getAppStatusMessageFromAppDetails } from './utils' const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; value: ReactNode; isLast?: boolean }) => ( @@ -37,17 +37,7 @@ const InfoCardItem = ({ heading, value, isLast = false }: { heading: string; val
) -const StatusHeadingContainer = ({ - children, - type, - appId, - envId, - actionItem, -}: PropsWithChildren> & { - appId: number - envId?: number - actionItem?: ReactNode -}) => ( +const StatusHeadingContainer = ({ children, type, appId, envId, actionItem }: StatusHeadingContainerProps) => (
{children} @@ -96,7 +86,7 @@ export const AppStatusBody = ({ return [ { - id: `app-status-block${1}`, + id: 'app-status-row', heading: type !== 'stack-manager' ? 'Application Status' : 'Status', value: ( diff --git a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx index 24e3dbbdd..c7cd462ac 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModal.component.tsx @@ -7,7 +7,11 @@ import { Drawer } from '@Common/Drawer' import { GenericEmptyState } from '@Common/EmptyState' import { handleUTCTime, stopPropagation, useAsync } from '@Common/Helper' import { DeploymentAppTypes, ImageType } from '@Common/Types' -import { ComponentSizeType } from '@Shared/constants' +import { + APP_DETAILS_FALLBACK_POLLING_INTERVAL, + ComponentSizeType, + PROGRESSING_DEPLOYMENT_STATUS_POLLING_INTERVAL, +} from '@Shared/constants' import { AppType } from '@Shared/types' import { APIResponseHandler } from '../APIResponseHandler' @@ -56,6 +60,10 @@ const AppStatusModal = ({ return response } + /** + * Fetching logic for app details is we initially call from useAsync then through useEffect initiate polling + * Since the dependency of useAsync is empty array, it will only be called once and then we will call the polling method is triggered based on the polling interval set in the environment variables. + */ const [ areInitialAppDetailsLoading, fetchedAppDetails, @@ -100,6 +108,12 @@ const AppStatusModal = ({ return response } + /** + * Fetching logic for deployment status is we initially call from useAsync then through useEffect initiate polling + * Now on tab switch we need to clear the previous timeout and set a new one reason being tab would have changed and in polling method that would not be reflected since closure is created + * So we re-trigger useAsync to get the new data and set a new timeout + * resetOnChange is there so that user don't see the change in icon in tabs + */ const [ isDeploymentTimelineLoading, deploymentStatusDetailsBreakdownData, @@ -122,7 +136,7 @@ const AppStatusModal = ({ // eslint-disable-next-line @typescript-eslint/no-floating-promises handleAppDetailsExternalSync() }, - Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL) || 30000, + Number(window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL) || APP_DETAILS_FALLBACK_POLLING_INTERVAL, ) } @@ -136,7 +150,7 @@ const AppStatusModal = ({ appDetails.appType !== AppType.DEVTRON_HELM_CHART ? window._env_.DEVTRON_APP_DETAILS_POLLING_INTERVAL : window._env_.HELM_APP_DETAILS_POLLING_INTERVAL, - ) || 30000 + ) || APP_DETAILS_FALLBACK_POLLING_INTERVAL deploymentStatusPollingTimeoutRef.current = setTimeout( async () => { @@ -149,7 +163,7 @@ const AppStatusModal = ({ // eslint-disable-next-line @typescript-eslint/no-floating-promises handleDeploymentStatusExternalSync() }, - isDeploymentInProgress ? 10000 : pollingIntervalFromFlag, + isDeploymentInProgress ? PROGRESSING_DEPLOYMENT_STATUS_POLLING_INTERVAL : pollingIntervalFromFlag, ) } diff --git a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx index a75ebe7c7..f50950417 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusModalTabList.tsx @@ -79,7 +79,7 @@ const AppStatusModalTabList = ({ // Could have achieved via onDataLoad but, have done this through useEffect to avoid abrupt shift in case some tabs went missing after polling useEffect(() => { if (tabGroups.length && !selectedTab) { - handleSelectTab(tabGroups[0]?.id as AppStatusModalTabType) + handleSelectTab(tabGroups[0].id as AppStatusModalTabType) } }, []) diff --git a/src/Shared/Components/AppStatusModal/types.ts b/src/Shared/Components/AppStatusModal/types.ts index eca8a6c77..9ac3c4b0a 100644 --- a/src/Shared/Components/AppStatusModal/types.ts +++ b/src/Shared/Components/AppStatusModal/types.ts @@ -1,4 +1,4 @@ -import { FunctionComponent } from 'react' +import { FunctionComponent, PropsWithChildren, ReactNode } from 'react' import { APIOptions } from '@Common/Types' import { @@ -92,3 +92,9 @@ export interface AppStatusModalTabListProps extends Pick> { + appId: number + envId?: number + actionItem?: ReactNode +} diff --git a/src/Shared/Components/AppStatusModal/utils.tsx b/src/Shared/Components/AppStatusModal/utils.tsx index d02794500..f42b2bd8f 100644 --- a/src/Shared/Components/AppStatusModal/utils.tsx +++ b/src/Shared/Components/AppStatusModal/utils.tsx @@ -84,30 +84,19 @@ export const getShowDeploymentStatusModal = ({ type, appDetails, }: Pick): boolean => { - if (!appDetails) { - return false - } - - const isHelmOrDevtronApp = - appDetails.appType === AppType.DEVTRON_APP || appDetails.appType === AppType.DEVTRON_HELM_CHART - - if (type === 'stack-manager' || !isHelmOrDevtronApp) { + if ( + !appDetails || + type === 'stack-manager' || + (appDetails.appType !== AppType.DEVTRON_APP && appDetails.appType !== AppType.DEVTRON_HELM_CHART) + ) { return false } if (appDetails.appType === AppType.DEVTRON_HELM_CHART) { - if (!appDetails.lastDeployedTime || appDetails.deploymentAppType === DeploymentAppTypes.HELM) { - return false - } - - return true - } - - if (appDetails.releaseMode === ReleaseMode.MIGRATE_EXTERNAL_APPS && !appDetails.isPipelineTriggered) { - return false + return !!appDetails.lastDeployedTime && appDetails.deploymentAppType !== DeploymentAppTypes.HELM } - return true + return appDetails.releaseMode !== ReleaseMode.MIGRATE_EXTERNAL_APPS || appDetails.isPipelineTriggered } export const getEmptyViewImageFromHelmDeploymentStatus = ( diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx index 68a8c7210..0b23c111d 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx @@ -78,18 +78,18 @@ export const DeploymentStatusDetailRow = ({ return (
-
- {statusBreakDownType.subSteps?.map((items, index) => ( - // eslint-disable-next-line react/no-array-index-key -
+ {statusBreakDownType.subSteps?.map((items, index) => ( + // eslint-disable-next-line react/no-array-index-key +
+
{renderDeploymentTimelineIcon(items.icon)} {items.message}
- ))} -
+
+ ))} {statusBreakDownType.resourceDetails?.length ? (
-
+
{MANIFEST_STATUS_HEADERS.map((headerKey, index) => ( // eslint-disable-next-line react/no-array-index-key
diff --git a/src/Shared/Components/DeploymentStatusBreakdown/types.ts b/src/Shared/Components/DeploymentStatusBreakdown/types.ts index 811f83c19..6c0cc185e 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/types.ts +++ b/src/Shared/Components/DeploymentStatusBreakdown/types.ts @@ -20,7 +20,7 @@ export enum WorkflowRunnerStatusDTO { DEGRADED = 'Degraded', } -export interface HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType { +export interface ProcessUnableToFetchOrTimedOutStatusType { timelineData: DeploymentStatusBreakdownItemType timelineStatusType: DeploymentStatusTimelineType deploymentStatus: typeof DEPLOYMENT_STATUS.UNABLE_TO_FETCH | typeof DEPLOYMENT_STATUS.TIMED_OUT diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index b2e211b1e..a2576e95a 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -19,7 +19,7 @@ import { SUCCESSFUL_DEPLOYMENT_STATUS, WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP, } from './constants' -import { HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType } from './types' +import { ProcessUnableToFetchOrTimedOutStatusType } from './types' const getDefaultDeploymentStatusTimeline = ( data?: DeploymentStatusDetailsType, @@ -94,13 +94,13 @@ const getPredicate = } } -const handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus = ({ +const processUnableToFetchOrTimedOutStatus = ({ timelineData, timelineStatusType, deploymentStatus, statusLastFetchedAt, statusFetchCount, -}: HandleUpdateTimelineDataForTimedOutOrUnableToFetchStatusParamsType) => { +}: ProcessUnableToFetchOrTimedOutStatusType) => { timelineData.icon = deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH ? 'disconnect' : 'timed_out' timelineData.displaySubText = 'Unknown' timelineData.isCollapsed = false @@ -149,7 +149,7 @@ const processKubeCTLApply = ( } if (element.status === TIMELINE_STATUS.KUBECTL_APPLY_STARTED) { - timelineData.resourceDetails = element.resourceDetails?.filter( + timelineData.resourceDetails = (element.resourceDetails || []).filter( (item) => item.resourcePhase === tableData.currentPhase, ) @@ -176,7 +176,7 @@ const processKubeCTLApply = ( deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT || deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH ) { - handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus({ + processUnableToFetchOrTimedOutStatus({ timelineData, timelineStatusType: TIMELINE_STATUS.KUBECTL_APPLY, deploymentStatus, @@ -202,6 +202,24 @@ const processKubeCTLApply = ( } } +/** + * @description + * This function processes the deployment status details data and returns a breakdown of the deployment status. + * Cases it handles: + * 1. If timelines are not present, say the case of helm deployment, we will parse the wfrStatus and put the status and basic deployment info [triggeredBy, deploymentStartedOn, deploymentFinishedOn] into the breakdown data and return it. + * 2. In case of gitops: + * - There are five timelines in chronological order: + * - Deployment Initiated + * - Git commit + * - ArgoCD Sync + * - Kubectl Apply + * - App Health + * - Basic flow is we traverse the timelines in order, if find the last status for that specific timeline from response by traversing the timelines in reverse order. + * - If element is found, we will parse the status and set the icon, display text, time, etc. for that timeline and set the next timeline to inprogress. + * - If element is not found, we will parse on basis of factors like: + * - If this timeline is not inprogress and deploymentStatus is progressing, we will set the current timeline to waiting. + * - In similar fashion based on the deploymentStatus we will set the icon and display text for the timeline. + */ export const processDeploymentStatusDetailsData = ( data?: DeploymentStatusDetailsType, ): DeploymentStatusDetailsBreakdownDataType => { @@ -229,6 +247,8 @@ export const processDeploymentStatusDetailsData = ( } const isProgressing = PROGRESSING_DEPLOYMENT_STATUS.includes(deploymentStatus) + // This key will be used since argocd sync is manual or auto based on flag on BE. + // And in old data as well this timeline won't be present so in KUBECTL_APPLY timeline we will set the icon to success const isArgoCDSyncAvailable = data.timelines.some((timeline) => timeline.status.includes(TIMELINE_STATUS.ARGOCD_SYNC), ) @@ -263,7 +283,7 @@ export const processDeploymentStatusDetailsData = ( deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT) && timelineData.icon === 'inprogress' ) { - handleUpdateTimelineDataForTimedOutOrUnableToFetchStatus({ + processUnableToFetchOrTimedOutStatus({ timelineData, timelineStatusType, deploymentStatus, diff --git a/src/Shared/Components/TabGroup/TabGroup.types.ts b/src/Shared/Components/TabGroup/TabGroup.types.ts index c4a6cabfc..619b9a064 100644 --- a/src/Shared/Components/TabGroup/TabGroup.types.ts +++ b/src/Shared/Components/TabGroup/TabGroup.types.ts @@ -93,23 +93,35 @@ type TabTooltipProps = tooltipProps?: never } +/** + * Represents the properties for defining an icon in a tab group. + * This type allows for three configurations: + * + * 1. **Icon as a functional component or string**: + * - Use the `icon` property to specify either a functional component that renders an SVG or a string representing the name of the icon. + * - The `iconElement` property must not be provided in this case. + * + * 2. **Icon as a JSX element**: + * - Use the `iconElement` property to specify a JSX element representing the icon. + * - The `icon` property must not be provided in this case. + * + * 3. **No icon**: + * - Neither `icon` nor `iconElement` is provided, resulting in no icon being displayed. + * + */ type TabGroupIconProp = | { /** - * Icon to be displayed in the tab. - * This can either be a functional component that renders a SVG - * or a string representing the name of the icon to be rendered by the Icon component. + * A functional component rendering an SVG or a string representing the icon name. Mutually exclusive with `iconElement`. */ icon: React.FunctionComponent> | IconName iconElement?: never } | { + icon?: never /** - * Icon to be displayed in the tab. - * This can either be a functional component that renders a SVG - * or a string representing the name of the icon to be rendered by the Icon component. + * A JSX element representing the icon. Mutually exclusive with `icon`. */ - icon?: never iconElement: JSX.Element } | { diff --git a/src/Shared/constants.tsx b/src/Shared/constants.tsx index e60ec0945..501c21c70 100644 --- a/src/Shared/constants.tsx +++ b/src/Shared/constants.tsx @@ -577,3 +577,6 @@ export const DEPLOYMENT_STAGE_TO_NODE_MAP: Readonly Date: Wed, 14 May 2025 19:14:50 +0530 Subject: [PATCH 23/32] refactor: change currentPhase type from string to null for better type safety in processKubeCTLApply --- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index a2576e95a..f08f71dd6 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -121,10 +121,10 @@ const processKubeCTLApply = ( data: DeploymentStatusDetailsType, ) => { const tableData: { - currentPhase: DeploymentPhaseType | '' + currentPhase: DeploymentPhaseType | null currentTableData: DeploymentStatusBreakdownItemType['subSteps'] } = { - currentPhase: '', + currentPhase: null, currentTableData: [{ icon: 'success', message: 'Started by Argo CD' }], } From 3d00862364b61dd7ac6d343e33736b9d21d81488 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 14 May 2025 19:53:21 +0530 Subject: [PATCH 24/32] chore: update version from 1.13.0-pre-3 to 1.13.0-beta-1 in package.json and package-lock.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f8afd806..a87cde2e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-pre-3", + "version": "1.13.0-beta-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-pre-3", + "version": "1.13.0-beta-1", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 47fbe8eeb..25f5867cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-pre-3", + "version": "1.13.0-beta-1", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", From 3efb36b0a0cc5b58d9908e9d78ebda9887d8a061 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Wed, 14 May 2025 23:36:04 +0530 Subject: [PATCH 25/32] chore: update version to 1.13.0-beta-2 in package.json and package-lock.json; add error handling in deployment status processing --- package-lock.json | 4 +- package.json | 2 +- .../AppStatusModal/AppStatusBody.tsx | 1 + .../CICDHistory/DeploymentStatusBreakdown.tsx | 23 ++++++-- .../DeploymentStatusBreakdown/utils.tsx | 52 +++++++++++-------- src/Shared/types.ts | 4 ++ 6 files changed, 57 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index a87cde2e5..c0e680605 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-1", + "version": "1.13.0-beta-2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-1", + "version": "1.13.0-beta-2", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 25f5867cb..8c8914749 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-1", + "version": "1.13.0-beta-2", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx index 09dba91d2..4ac6ca1b8 100644 --- a/src/Shared/Components/AppStatusModal/AppStatusBody.tsx +++ b/src/Shared/Components/AppStatusModal/AppStatusBody.tsx @@ -184,6 +184,7 @@ export const AppStatusBody = ({ deploymentStatusDetailsBreakdownData={deploymentStatusDetailsBreakdownData} isVirtualEnvironment={appDetails.isVirtualEnvironment} appDetails={appDetails} + rootClassName="pb-20" /> )}
diff --git a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx index 4a26a54c9..5ba7e5b59 100644 --- a/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx +++ b/src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx @@ -14,8 +14,11 @@ * limitations under the License. */ +import { Fragment } from 'react' + import { TIMELINE_STATUS } from '@Shared/types' +import { InfoBlock } from '../InfoBlock' import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow' import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType } from './types' @@ -59,11 +62,21 @@ const DeploymentStatusDetailBreakdown = ({ TIMELINE_STATUS.KUBECTL_APPLY, ] as DeploymentStatusDetailRowType['type'][] ).map((timelineStatus) => ( - + + {deploymentStatusDetailsBreakdownData.errorBarConfig?.nextTimelineToProcess === + timelineStatus && ( + <> + +
+ + )} + + ))} timeline.status === TIMELINE_STATUS.DEPLOYMENT_FAILED) + ?.statusDetail || '' + : '' + return { - deploymentStatus: WFR_STATUS_DTO_TO_DEPLOYMENT_STATUS_MAP[data?.wfrStatus] || DEPLOYMENT_STATUS.INPROGRESS, + deploymentStatus, deploymentTriggerTime: data?.deploymentStartedOn || '', deploymentEndTime: data?.deploymentFinishedOn || '', triggeredBy: data?.triggeredBy || '', @@ -65,6 +73,12 @@ const getDefaultDeploymentStatusTimeline = ( displayText: 'Propagate manifest to Kubernetes resources', }, }, + errorBarConfig: deploymentErrorMessage + ? { + deploymentErrorMessage, + nextTimelineToProcess: TIMELINE_STATUS.GIT_COMMIT, + } + : null, } } @@ -85,9 +99,7 @@ const getPredicate = return timelineItem.status.includes(TIMELINE_STATUS.KUBECTL_APPLY) case TIMELINE_STATUS.APP_HEALTH: - return [TIMELINE_STATUS.HEALTHY, TIMELINE_STATUS.DEGRADED, TIMELINE_STATUS.DEPLOYMENT_FAILED].includes( - timelineItem.status, - ) + return [TIMELINE_STATUS.HEALTHY, TIMELINE_STATUS.DEGRADED].includes(timelineItem.status) default: return false @@ -247,6 +259,7 @@ export const processDeploymentStatusDetailsData = ( } const isProgressing = PROGRESSING_DEPLOYMENT_STATUS.includes(deploymentStatus) + const hasDeploymentFailed = deploymentStatus === DEPLOYMENT_STATUS.FAILED // This key will be used since argocd sync is manual or auto based on flag on BE. // And in old data as well this timeline won't be present so in KUBECTL_APPLY timeline we will set the icon to success const isArgoCDSyncAvailable = data.timelines.some((timeline) => @@ -255,7 +268,6 @@ export const processDeploymentStatusDetailsData = ( PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER.forEach((timelineStatusType, index) => { const element = findRight(data.timelines, getPredicate(timelineStatusType)) - const timelineData = deploymentData.deploymentStatusBreakdown[timelineStatusType] if (!element) { @@ -265,6 +277,7 @@ export const processDeploymentStatusDetailsData = ( timelineData.displaySubText = 'Waiting' } + // We don't even need to clean this in final loop since deployment status won't be in progress if next timeline is progressing if (isProgressing && timelineStatusType === TIMELINE_STATUS.KUBECTL_APPLY) { timelineData.subSteps = [ { icon: '', message: 'Waiting to be started by Argo CD' }, @@ -273,9 +286,13 @@ export const processDeploymentStatusDetailsData = ( timelineData.isCollapsed = false } - if (deploymentStatus === DEPLOYMENT_STATUS.FAILED) { - timelineData.displaySubText = '' - timelineData.icon = 'unreachable' + if (hasDeploymentFailed) { + const hasCurrentTimelineFailed = + timelineStatusType === TIMELINE_STATUS.APP_HEALTH && + deploymentData.deploymentStatusBreakdown.KUBECTL_APPLY.icon === 'success' + + timelineData.displaySubText = hasCurrentTimelineFailed ? 'Failed' : '' + timelineData.icon = hasCurrentTimelineFailed ? 'failed' : 'unreachable' } if ( @@ -291,7 +308,6 @@ export const processDeploymentStatusDetailsData = ( statusFetchCount: data?.statusFetchCount, }) } - return } @@ -327,18 +343,8 @@ export const processDeploymentStatusDetailsData = ( case TIMELINE_STATUS.APP_HEALTH: timelineData.time = element.statusTime - if (element.status === TIMELINE_STATUS.DEPLOYMENT_FAILED) { - // TODO: Check why its icon is not failed in earlier implementation - timelineData.icon = 'failed' - timelineData.displaySubText = 'Failed' - timelineData.timelineStatus = element.statusDetail - break - } - - if (element.status === TIMELINE_STATUS.HEALTHY || element.status === TIMELINE_STATUS.DEGRADED) { - timelineData.icon = 'success' - timelineData.displaySubText = element.status === TIMELINE_STATUS.HEALTHY ? '' : 'Degraded' - } + timelineData.icon = 'success' + timelineData.displaySubText = element.status === TIMELINE_STATUS.HEALTHY ? '' : 'Degraded' break default: @@ -350,6 +356,10 @@ export const processDeploymentStatusDetailsData = ( const nextTimelineStatus = PHYSICAL_ENV_DEPLOYMENT_TIMELINE_ORDER[index + 1] const nextTimeline = deploymentData.deploymentStatusBreakdown[nextTimelineStatus] + if (deploymentData.errorBarConfig) { + deploymentData.errorBarConfig.nextTimelineToProcess = nextTimelineStatus + } + nextTimeline.icon = 'inprogress' nextTimeline.displaySubText = 'In progress' } diff --git a/src/Shared/types.ts b/src/Shared/types.ts index a0fed9ca1..f5efd13e8 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -1274,6 +1274,10 @@ export interface DeploymentStatusDetailsBreakdownDataType { deploymentEndTime: string triggeredBy: string deploymentStatusBreakdown: Partial> + errorBarConfig?: { + deploymentErrorMessage: string + nextTimelineToProcess: DeploymentStatusTimelineType + } | null } export interface IntelligenceConfig { From 115493759e4c6df1c366b1bbc7dc8b756e323de7 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Thu, 15 May 2025 00:13:24 +0530 Subject: [PATCH 26/32] chore: update version to 1.13.0-beta-3 in package.json and package-lock.json; enhance deployment status details processing --- package-lock.json | 4 ++-- package.json | 2 +- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c0e680605..4b9aa5289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-2", + "version": "1.13.0-beta-3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-2", + "version": "1.13.0-beta-3", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 8c8914749..92ebdd115 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-2", + "version": "1.13.0-beta-3", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 1bcbfb2b7..9b29f1d5e 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -376,6 +376,8 @@ export const processDeploymentStatusDetailsData = ( const prevTimelineData = deploymentData.deploymentStatusBreakdown[prevTimelineStatusType] prevTimelineData.icon = 'success' prevTimelineData.displaySubText = '' + prevTimelineData.isCollapsed = false + prevTimelineData.timelineStatus = '' } break } From 6eaf709dd94c209317dbb3f4a5dd969c29fe0e47 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Thu, 15 May 2025 00:28:39 +0530 Subject: [PATCH 27/32] chore: update version to 1.13.0-beta-4 in package.json and package-lock.json; modify deployment status collapse behavior --- package-lock.json | 4 ++-- package.json | 2 +- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b9aa5289..648fad28c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-3", + "version": "1.13.0-beta-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-3", + "version": "1.13.0-beta-4", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index 92ebdd115..ffbc7f943 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-3", + "version": "1.13.0-beta-4", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index 9b29f1d5e..df0ce16fc 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -376,7 +376,7 @@ export const processDeploymentStatusDetailsData = ( const prevTimelineData = deploymentData.deploymentStatusBreakdown[prevTimelineStatusType] prevTimelineData.icon = 'success' prevTimelineData.displaySubText = '' - prevTimelineData.isCollapsed = false + prevTimelineData.isCollapsed = true prevTimelineData.timelineStatus = '' } break From 7868287f0d30d47ac3b93d3fe39b11e21b33af62 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Thu, 15 May 2025 07:37:18 +0530 Subject: [PATCH 28/32] chore: update version to 1.13.0-beta-5 in package.json and package-lock.json; set ARGOCD_SYNC collapse state to true --- package-lock.json | 4 ++-- package.json | 2 +- src/Shared/Components/DeploymentStatusBreakdown/utils.tsx | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 648fad28c..1ddf4e19e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-4", + "version": "1.13.0-beta-5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-4", + "version": "1.13.0-beta-5", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/package.json b/package.json index ffbc7f943..0d86976fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "1.13.0-beta-4", + "version": "1.13.0-beta-5", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx index df0ce16fc..89a232fe0 100644 --- a/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx +++ b/src/Shared/Components/DeploymentStatusBreakdown/utils.tsx @@ -335,6 +335,7 @@ export const processDeploymentStatusDetailsData = ( deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.icon = 'success' deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.displaySubText = '' deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.time = element.statusTime + deploymentData.deploymentStatusBreakdown.ARGOCD_SYNC.isCollapsed = true } processKubeCTLApply(timelineData, element, deploymentStatus, data) From 05c985c3144a9c848915482097864aa52ddc3d9c Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Thu, 15 May 2025 16:46:29 +0530 Subject: [PATCH 29/32] feat: add logsRendererRef to LogStageAccordion and LogsRenderer components --- src/Shared/Components/CICDHistory/LogStageAccordion.tsx | 8 +++++++- src/Shared/Components/CICDHistory/LogsRenderer.tsx | 8 +++++++- src/Shared/Components/CICDHistory/types.tsx | 3 ++- .../TargetPlatforms/TargetPlatformListTooltip.tsx | 3 ++- src/Shared/Components/TargetPlatforms/types.ts | 6 +++--- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Shared/Components/CICDHistory/LogStageAccordion.tsx b/src/Shared/Components/CICDHistory/LogStageAccordion.tsx index aa21f4c34..cfb9577d9 100644 --- a/src/Shared/Components/CICDHistory/LogStageAccordion.tsx +++ b/src/Shared/Components/CICDHistory/LogStageAccordion.tsx @@ -43,6 +43,7 @@ const LogStageAccordion = ({ fullScreenView, searchIndex, targetPlatforms, + logsRendererRef, }: LogStageAccordionProps) => { const handleAccordionToggle = () => { if (isOpen) { @@ -78,6 +79,8 @@ const LogStageAccordion = ({ } } + const getLogsRendererReference = () => logsRendererRef.current + return (