Skip to content

Commit c1151ca

Browse files
committed
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.
1 parent 4bf5e2c commit c1151ca

File tree

18 files changed

+755
-645
lines changed

18 files changed

+755
-645
lines changed

src/Common/Helper.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
1818
import DOMPurify from 'dompurify'
1919
import { JSONPath, JSONPathOptions } from 'jsonpath-plus'
20-
import { compare as compareJSON, applyPatch, unescapePathComponent,deepClone } from 'fast-json-patch'
20+
import { compare as compareJSON, applyPatch, unescapePathComponent, deepClone } from 'fast-json-patch'
2121
import { components } from 'react-select'
2222
import * as Sentry from '@sentry/browser'
2323
import moment from 'moment'
@@ -516,7 +516,7 @@ export const getUrlWithSearchParams = <T extends string | number = string | numb
516516
/**
517517
* Custom exception logger function for logging errors to sentry
518518
*/
519-
export const logExceptionToSentry: typeof Sentry.captureException = Sentry.captureException.bind(window)
519+
export const logExceptionToSentry: typeof Sentry.captureException = Sentry.captureException.bind(window)
520520

521521
export const customStyles = {
522522
control: (base, state) => ({
@@ -614,7 +614,10 @@ const buildObjectFromPathTokens = (index: number, tokens: string[], value: any)
614614
const numberKey = Number(key)
615615
const isKeyNumber = !Number.isNaN(numberKey)
616616
return isKeyNumber
617-
? [...Array(numberKey).fill(UNCHANGED_ARRAY_ELEMENT_SYMBOL), buildObjectFromPathTokens(index + 1, tokens, value)]
617+
? [
618+
...Array(numberKey).fill(UNCHANGED_ARRAY_ELEMENT_SYMBOL),
619+
buildObjectFromPathTokens(index + 1, tokens, value),
620+
]
618621
: { [unescapePathComponent(key)]: buildObjectFromPathTokens(index + 1, tokens, value) }
619622
}
620623

@@ -664,7 +667,12 @@ export const powerSetOfSubstringsFromStart = (strings: string[], regex: RegExp)
664667
})
665668

666669
export const convertJSONPointerToJSONPath = (pointer: string) =>
667-
unescapePathComponent(pointer.replace(/\/([\*0-9]+)\//g, '[$1].').replace(/\//g, '.').replace(/\./, '$.'))
670+
unescapePathComponent(
671+
pointer
672+
.replace(/\/([\*0-9]+)\//g, '[$1].')
673+
.replace(/\//g, '.')
674+
.replace(/\./, '$.'),
675+
)
668676

669677
export const flatMapOfJSONPaths = (
670678
paths: string[],
@@ -1001,7 +1009,7 @@ export const getBranchIcon = (sourceType, _isRegex?: boolean, webhookEventName?:
10011009
return <ICPullRequest className="scn-6" />
10021010
}
10031011
if (webhookEventName === WebhookEventNameType.TAG_CREATION) {
1004-
return <ICTag className="scn-6" />
1012+
return <ICTag className="scn-6" />
10051013
}
10061014
return <ICWebhook />
10071015
}
@@ -1025,7 +1033,6 @@ export const getIframeWithDefaultAttributes = (iframeString: string, defaultName
10251033
const parentDiv = document.createElement('div')
10261034
parentDiv.innerHTML = getSanitizedIframe(iframeString)
10271035

1028-
10291036
const iframe = parentDiv.querySelector('iframe')
10301037
if (iframe) {
10311038
if (!iframe.hasAttribute('title') && !!defaultName) {
@@ -1094,5 +1101,15 @@ export const getTTLInHumanReadableFormat = (ttl: number): string => {
10941101
}
10951102
const humanizedDuration = moment.duration(absoluteTTL, 'seconds').humanize(false)
10961103
// Since moment.js return "a" or "an" for singular values so replacing with 1.
1097-
return humanizedDuration.replace(/^(a|an) /, '1 ');
1098-
}
1104+
return humanizedDuration.replace(/^(a|an) /, '1 ')
1105+
}
1106+
1107+
export const findRight = <T,>(arr: T[], predicate: (item: T) => boolean): T => {
1108+
for (let i = arr.length - 1; i >= 0; i--) {
1109+
if (predicate(arr[i])) {
1110+
return arr[i]
1111+
}
1112+
}
1113+
1114+
return null
1115+
}

src/Shared/Components/AppStatusModal/service.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { get, getIsRequestAborted } from '@Common/API'
22
import { ROUTES } from '@Common/Constants'
33
import { getUrlWithSearchParams, showError } from '@Common/Helper'
4-
import { processDeploymentStatusDetailsData } from '@Shared/Helpers'
5-
import { AppDetails, AppType } from '@Shared/types'
4+
import {
5+
AppDetails,
6+
AppType,
7+
DeploymentStatusDetailsBreakdownDataType,
8+
DeploymentStatusDetailsType,
9+
} from '@Shared/types'
610

7-
import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory'
11+
import { processDeploymentStatusDetailsData } from '../DeploymentStatusBreakdown'
812
import { GetAppDetailsParamsType } from './types'
913

1014
export const getAppDetails = async ({

src/Shared/Components/AppStatusModal/types.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { FunctionComponent } from 'react'
22

33
import { APIOptions } from '@Common/Types'
4-
import { AppDetails, ConfigDriftModalProps } from '@Shared/types'
5-
6-
import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType } from '../CICDHistory'
4+
import {
5+
AppDetails,
6+
ConfigDriftModalProps,
7+
DeploymentStatusDetailsBreakdownDataType,
8+
DeploymentStatusDetailsType,
9+
} from '@Shared/types'
710

811
export enum AppStatusModalTabType {
912
APP_STATUS = 'appStatus',

src/Shared/Components/CICDHistory/DeploymentDetailSteps.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,19 @@ import { useEffect, useRef, useState } from 'react'
1818
import { useHistory, useParams, useRouteMatch } from 'react-router-dom'
1919

2020
import { IndexStore } from '@Shared/Store'
21+
import { DeploymentStatusDetailsBreakdownDataType, DeploymentStatusDetailsType, TIMELINE_STATUS } from '@Shared/types'
2122

2223
import { ReactComponent as Arrow } from '../../../Assets/Icon/ic-arrow-forward.svg'
2324
import mechanicalOperation from '../../../Assets/Icon/ic-mechanical-operation.svg'
2425
import { DeploymentAppTypes, GenericEmptyState, Progressing, URLS } from '../../../Common'
25-
import { DEPLOYMENT_STATUS, EMPTY_STATE_STATUS, TIMELINE_STATUS } from '../../constants'
26-
import { getHandleOpenURL, getIsApprovalPolicyConfigured, processDeploymentStatusDetailsData } from '../../Helpers'
26+
import { DEPLOYMENT_STATUS, EMPTY_STATE_STATUS } from '../../constants'
27+
import { getHandleOpenURL, getIsApprovalPolicyConfigured } from '../../Helpers'
28+
import { processDeploymentStatusDetailsData } from '../DeploymentStatusBreakdown'
2729
import CDEmptyState from './CDEmptyState'
2830
import { DEPLOYMENT_STATUS_QUERY_PARAM } from './constants'
2931
import DeploymentStatusDetailBreakdown from './DeploymentStatusBreakdown'
3032
import { getDeploymentStatusDetail } from './service'
31-
import {
32-
DeploymentDetailStepsType,
33-
DeploymentStatusDetailsBreakdownDataType,
34-
DeploymentStatusDetailsType,
35-
} from './types'
33+
import { DeploymentDetailStepsType } from './types'
3634

3735
let deploymentStatusTimer = null
3836
const DeploymentDetailSteps = ({

src/Shared/Components/CICDHistory/DeploymentStatusBreakdown.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
import { Fragment } from 'react'
1818
import { useRouteMatch } from 'react-router-dom'
1919

20+
import { TIMELINE_STATUS } from '@Shared/types'
21+
2022
import { URLS } from '../../../Common'
21-
import { TIMELINE_STATUS } from '../../constants'
2223
import ErrorBar from '../Error/ErrorBar'
2324
import { DeploymentStatusDetailRow } from './DeploymentStatusDetailRow'
24-
import { ErrorInfoStatusBar } from './ErrorInfoStatusBar'
25-
import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType, ErrorInfoStatusBarType } from './types'
25+
import { DeploymentStatusDetailBreakdownType, DeploymentStatusDetailRowType } from './types'
2626

2727
import './DeploymentStatusBreakdown.scss'
2828

@@ -43,11 +43,6 @@ const DeploymentStatusDetailBreakdown = ({
4343
deploymentDetailedData: deploymentStatusDetailsBreakdownData,
4444
}
4545

46-
const errorInfoStatusBarProps: Pick<ErrorInfoStatusBarType, 'lastFailedStatusType' | 'errorMessage'> = {
47-
lastFailedStatusType: deploymentStatusDetailsBreakdownData.nonDeploymentError,
48-
errorMessage: deploymentStatusDetailsBreakdownData.deploymentError,
49-
}
50-
5146
return (
5247
<>
5348
{!url.includes(`/${URLS.CD_DETAILS}`) && <ErrorBar appDetails={appDetails} />}
@@ -63,17 +58,17 @@ const DeploymentStatusDetailBreakdown = ({
6358
]
6459
) ? (
6560
<>
66-
{[TIMELINE_STATUS.GIT_COMMIT, TIMELINE_STATUS.ARGOCD_SYNC, TIMELINE_STATUS.KUBECTL_APPLY].map(
67-
(timelineStatus) => (
68-
<Fragment key={timelineStatus}>
69-
<ErrorInfoStatusBar type={timelineStatus} {...errorInfoStatusBarProps} />
70-
<DeploymentStatusDetailRow
71-
type={timelineStatus}
72-
{...deploymentStatusDetailRowProps}
73-
/>
74-
</Fragment>
75-
),
76-
)}
61+
{(
62+
[
63+
TIMELINE_STATUS.GIT_COMMIT,
64+
TIMELINE_STATUS.ARGOCD_SYNC,
65+
TIMELINE_STATUS.KUBECTL_APPLY,
66+
] as DeploymentStatusDetailRowType['type'][]
67+
).map((timelineStatus) => (
68+
<Fragment key={timelineStatus}>
69+
<DeploymentStatusDetailRow type={timelineStatus} {...deploymentStatusDetailRowProps} />
70+
</Fragment>
71+
))}
7772

7873
<DeploymentStatusDetailRow
7974
type={TIMELINE_STATUS.APP_HEALTH}

src/Shared/Components/CICDHistory/DeploymentStatusDetailRow.tsx

Lines changed: 45 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,17 @@ import { useParams } from 'react-router-dom'
2020
import moment from 'moment'
2121

2222
import { ShowMoreText } from '@Shared/Components/ShowMoreText'
23-
import { AppType } from '@Shared/types'
23+
import { AppType, TIMELINE_STATUS } from '@Shared/types'
2424

2525
import { ReactComponent as DropDownIcon } from '../../../Assets/Icon/ic-chevron-down.svg'
2626
import { DATE_TIME_FORMATS, showError } from '../../../Common'
27-
import { ComponentSizeType, DEPLOYMENT_STATUS, statusIcon, TIMELINE_STATUS } from '../../constants'
27+
import { ComponentSizeType, DEPLOYMENT_STATUS, statusIcon } from '../../constants'
2828
import { AppStatusContent } from '../AppStatusModal'
2929
import { Button, ButtonStyleType, ButtonVariantType } from '../Button'
3030
import { APP_HEALTH_DROP_DOWN_LIST, MANIFEST_STATUS_HEADERS, TERMINAL_STATUS_MAP } from './constants'
31-
import { ErrorInfoStatusBar } from './ErrorInfoStatusBar'
3231
import { getManualSync } from './service'
3332
import { DeploymentStatusDetailRowType } from './types'
34-
import { renderIcon } from './utils'
33+
import { getDeploymentTimelineBGColorFromIcon, renderDeploymentTimelineIcon } from './utils'
3534

3635
export const DeploymentStatusDetailRow = ({
3736
type,
@@ -43,19 +42,22 @@ export const DeploymentStatusDetailRow = ({
4342
// Can't use appDetails directly as in case of deployment history, appDetails will be null
4443
const { appId: paramAppId, envId: paramEnvId } = useParams<{ appId: string; envId: string }>()
4544

45+
const [isManualSyncLoading, setIsManualSyncLoading] = useState<boolean>(false)
46+
4647
const statusBreakDownType = deploymentDetailedData.deploymentStatusBreakdown[type]
47-
const [collapsed, toggleCollapsed] = useState<boolean>(statusBreakDownType.isCollapsed)
48+
const [isCollapsed, setIsCollapsed] = useState<boolean>(statusBreakDownType.isCollapsed)
4849

4950
const isHelmManifestPushFailed =
5051
type === TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO &&
5152
deploymentDetailedData.deploymentStatus === statusIcon.failed
5253

5354
useEffect(() => {
54-
toggleCollapsed(statusBreakDownType.isCollapsed)
55+
setIsCollapsed(statusBreakDownType.isCollapsed)
5556
}, [statusBreakDownType.isCollapsed])
5657

5758
const manualSyncData = async () => {
5859
try {
60+
setIsManualSyncLoading(true)
5961
const { appId: appDetailsAppId, appType, environmentId: appDetailsEnvId, installedAppId } = appDetails || {}
6062
const parsedAppIdFromAppDetails = appType === AppType.DEVTRON_HELM_CHART ? installedAppId : appDetailsAppId
6163

@@ -65,10 +67,12 @@ export const DeploymentStatusDetailRow = ({
6567
await getManualSync({ appId, envId })
6668
} catch (error) {
6769
showError(error)
70+
} finally {
71+
setIsManualSyncLoading(false)
6872
}
6973
}
7074
const toggleDropdown = () => {
71-
toggleCollapsed(!collapsed)
75+
setIsCollapsed(!isCollapsed)
7276
}
7377

7478
const renderDetailedData = () => {
@@ -78,16 +82,15 @@ export const DeploymentStatusDetailRow = ({
7882

7983
return (
8084
<div className="px-8 py-12">
81-
<div className="">
82-
{deploymentDetailedData.deploymentStatusBreakdown[TIMELINE_STATUS.KUBECTL_APPLY].kubeList?.map(
83-
(items, index) => (
84-
// eslint-disable-next-line react/no-array-index-key
85-
<div className="flex left lh-20 mb-8" key={`item-${index}`}>
86-
{renderIcon(items.icon)}
87-
<span className="ml-12">{items.message}</span>
88-
</div>
89-
),
90-
)}
85+
<div>
86+
{/* TODO: Can be statusBreakDownType */}
87+
{statusBreakDownType.subSteps?.map((items, index) => (
88+
// eslint-disable-next-line react/no-array-index-key
89+
<div className="flex left lh-20 mb-8" key={`item-${index}`}>
90+
{renderDeploymentTimelineIcon(items.icon)}
91+
<span className="ml-12">{items.message}</span>
92+
</div>
93+
))}
9194
</div>
9295
{statusBreakDownType.resourceDetails?.length ? (
9396
<div className="pl-32">
@@ -129,24 +132,30 @@ export const DeploymentStatusDetailRow = ({
129132
)
130133
}
131134

132-
const renderErrorInfoBar = () => (
133-
<ErrorInfoStatusBar
134-
type={TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO}
135-
lastFailedStatusType={deploymentDetailedData.nonDeploymentError}
136-
errorMessage={deploymentDetailedData.deploymentError}
137-
hideVerticalConnector
138-
hideErrorIcon
139-
/>
140-
)
135+
const renderErrorInfoBar = () => {
136+
if (deploymentDetailedData.lastFailedStatusType !== TIMELINE_STATUS.HELM_MANIFEST_PUSHED_TO_HELM_REPO) {
137+
return null
138+
}
139+
140+
return (
141+
<div className="bcr-1 fs-13 p-8">
142+
<span className="dc__word-break lh-20">{deploymentDetailedData.deploymentError}</span>
143+
<ol className="m-0 pl-20">
144+
<li>Ensure provided repository path is valid</li>
145+
<li>Check if credentials provided for OCI registry are valid and have PUSH permission</li>
146+
</ol>
147+
</div>
148+
)
149+
}
141150

142151
const isAccordion =
143-
(type === TIMELINE_STATUS.KUBECTL_APPLY && statusBreakDownType.kubeList?.length) ||
152+
statusBreakDownType.subSteps?.length ||
144153
(type === TIMELINE_STATUS.APP_HEALTH && APP_HEALTH_DROP_DOWN_LIST.includes(statusBreakDownType.icon)) ||
145154
((type === TIMELINE_STATUS.GIT_COMMIT || type === TIMELINE_STATUS.ARGOCD_SYNC) &&
146155
statusBreakDownType.icon === 'failed')
147156

148157
const renderAccordionDetails = () => {
149-
if (!isAccordion || !collapsed) {
158+
if (isCollapsed) {
150159
return null
151160
}
152161

@@ -158,9 +167,7 @@ export const DeploymentStatusDetailRow = ({
158167
statusBreakDownType.icon !== 'inprogress' ? 'bcr-1' : 'bcy-2'
159168
}`}
160169
>
161-
{type === TIMELINE_STATUS.APP_HEALTH
162-
? statusBreakDownType.timelineStatus
163-
: deploymentDetailedData.deploymentStatusBreakdown[type].timelineStatus}
170+
{statusBreakDownType.timelineStatus}
164171

165172
{(deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.TIMED_OUT ||
166173
deploymentDetailedData.deploymentStatus === DEPLOYMENT_STATUS.UNABLE_TO_FETCH) && (
@@ -170,6 +177,7 @@ export const DeploymentStatusDetailRow = ({
170177
variant={ButtonVariantType.text}
171178
size={ComponentSizeType.xxs}
172179
onClick={manualSyncData}
180+
isLoading={isManualSyncLoading}
173181
/>
174182
)}
175183
</div>
@@ -190,10 +198,10 @@ export const DeploymentStatusDetailRow = ({
190198
<>
191199
<div className="bw-1 en-2">
192200
<div
193-
className={`flexbox dc__align-items-center dc__content-space dc__gap-12 py-8 px-8 bg__primary ${collapsed ? (!isHelmManifestPushFailed ? 'br-4' : '') : 'border-collapse'}`}
201+
className={`flexbox dc__align-items-center dc__content-space dc__gap-12 py-8 px-8 bg__primary ${isCollapsed ? (!isHelmManifestPushFailed ? 'br-4' : '') : 'border-collapse'}`}
194202
>
195203
<div className="flexbox dc__align-items-center dc__gap-12 flex-grow-1">
196-
{renderIcon(statusBreakDownType.icon)}
204+
{renderDeploymentTimelineIcon(statusBreakDownType.icon)}
197205
<span className="fs-13 flexbox dc__gap-6">
198206
<span data-testid="deployment-status-step-name" className="dc__truncate">
199207
{statusBreakDownType.displayText}
@@ -208,9 +216,9 @@ export const DeploymentStatusDetailRow = ({
208216
{statusBreakDownType.time !== '' && statusBreakDownType.icon !== 'inprogress' && (
209217
<span
210218
data-testid="deployment-status-kubernetes-dropdown dc__no-shrink"
211-
className={`px-8 py-4 br-12 ${
212-
statusBreakDownType.icon === 'failed' ? 'bcr-1 cr-5' : 'bcg-1 cg-7'
213-
}`}
219+
className={`px-8 py-4 br-12 ${getDeploymentTimelineBGColorFromIcon(
220+
statusBreakDownType.icon,
221+
)}`}
214222
>
215223
{moment(statusBreakDownType.time, 'YYYY-MM-DDTHH:mm:ssZ').format(
216224
DATE_TIME_FORMATS.TWELVE_HOURS_FORMAT,
@@ -229,7 +237,7 @@ export const DeploymentStatusDetailRow = ({
229237
<DropDownIcon
230238
style={{
231239
marginLeft: 'auto',
232-
['--rotateBy' as any]: `${180 * Number(!!collapsed)}deg`,
240+
['--rotateBy' as any]: `${180 * Number(!isCollapsed)}deg`,
233241
}}
234242
className="rotate"
235243
/>

0 commit comments

Comments
 (0)