Skip to content

Commit be5ce8e

Browse files
authored
Merge pull request #1512 from devtron-labs/feat/skip-bulk-ci
feat: skipped status for change branch
2 parents 86e9a2e + 40f706d commit be5ce8e

File tree

8 files changed

+96
-23
lines changed

8 files changed

+96
-23
lines changed

src/components/ApplicationGroup/Constants.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export enum BulkResponseStatus {
4545
'PASS' = 'pass',
4646
'FAIL' = 'fail',
4747
'UNAUTHORIZE' = 'unauthorized',
48+
'SKIP' = 'skip',
4849
}
4950

5051
export const BULK_VIRTUAL_RESPONSE_STATUS = {
@@ -180,6 +181,8 @@ export const CREATE_GROUP_TABS = {
180181

181182
export const GetBranchChangeStatus = (statusText: string): BulkResponseStatus => {
182183
switch (statusText) {
184+
case BulkResponseStatus.SKIP:
185+
return BulkResponseStatus.SKIP
183186
case BULK_VIRTUAL_RESPONSE_STATUS.pass:
184187
return BulkResponseStatus.PASS
185188
case BULK_VIRTUAL_RESPONSE_STATUS.fail:
@@ -191,4 +194,6 @@ export const GetBranchChangeStatus = (statusText: string): BulkResponseStatus =>
191194
}
192195
}
193196

194-
export const FILTER_NAME_REGEX = /^[a-z][a-z0-9-]{1,}[a-z0-9]$/
197+
export const FILTER_NAME_REGEX = /^[a-z][a-z0-9-]{1,}[a-z0-9]$/
198+
export const SKIPPED_RESOURCES_MESSAGE = 'Build action is not applicable'
199+
export const SKIPPED_RESOURCES_STATUS_TEXT = 'Skipped'

src/components/ApplicationGroup/Details/TriggerView/BulkCITrigger.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,8 @@ export default function BulkCITrigger({
554554
)
555555
}
556556

557-
const onClickStartBuild = (): void => {
557+
const onClickStartBuild = (e: React.MouseEvent): void => {
558+
e.stopPropagation()
558559
onClickTriggerBulkCI(appIgnoreCache)
559560
}
560561

src/components/ApplicationGroup/Details/TriggerView/BulkSourceChange.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import React, { useEffect, useRef, useState } from 'react'
33
import { ReactComponent as Close } from '../../../../assets/icons/ic-cross.svg'
44
import { ReactComponent as Warn } from '../../../../assets/icons/ic-warning.svg'
55
import SourceUpdateResponseModal from './SourceUpdateResponseModal'
6+
import { BulkSourceChangeProps } from './types'
67

7-
export default function BulkSourceChange({ closePopup, responseList, changeBranch, loading, selectedAppCount }) {
8+
export default function BulkSourceChange({ closePopup, responseList, changeBranch, loading, selectedAppCount }: BulkSourceChangeProps) {
89
const sourceChangeDetailRef = useRef<HTMLDivElement>(null)
910

1011
const [showResponseModal, setShowResponseModal] = useState(false)
@@ -49,7 +50,8 @@ export default function BulkSourceChange({ closePopup, responseList, changeBranc
4950
setShowResponseModal(responseList.length > 0)
5051
}, [responseList])
5152

52-
const updateBranch = () => {
53+
const updateBranch = (e: React.MouseEvent) => {
54+
e.stopPropagation()
5355
if (branchName.length === 0) {
5456
setInputError('This is required')
5557
return
@@ -137,7 +139,7 @@ export default function BulkSourceChange({ closePopup, responseList, changeBranc
137139
<div className="dc__window-bg h-100 bcn-0 bulk-ci-trigger-container" ref={sourceChangeDetailRef}>
138140
{renderHeaderSection()}
139141
{showResponseModal ? (
140-
<SourceUpdateResponseModal closePopup={closePopup} isLoading={false} responseList={responseList} />
142+
<SourceUpdateResponseModal closePopup={closePopup} isLoading={loading} responseList={responseList}/>
141143
) : (
142144
<>
143145
{renderInfoBar()}

src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ import {
5757
BULK_CD_RESPONSE_STATUS_TEXT,
5858
BULK_VIRTUAL_RESPONSE_STATUS,
5959
GetBranchChangeStatus,
60+
SKIPPED_RESOURCES_STATUS_TEXT,
61+
SKIPPED_RESOURCES_MESSAGE,
6062
} from '../../Constants'
6163
import { ReactComponent as DeployIcon } from '../../../../assets/icons/ic-nav-rocket.svg'
6264
import { ReactComponent as Close } from '../../../../assets/icons/ic-cross.svg'
@@ -1001,8 +1003,13 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
10011003
setFilteredWorkflows(_workflows)
10021004
}
10031005

1006+
const isBuildAndBranchTriggerAllowed = (node: NodeAttr): boolean => (
1007+
!node.isLinkedCI && !node.isLinkedCD && node.type !== WorkflowNodeType.WEBHOOK
1008+
)
1009+
10041010
const changeBranch = (value): void => {
10051011
const appIds = []
1012+
const skippedResources = []
10061013
const appNameMap = new Map()
10071014

10081015
filteredWorkflows.forEach((wf) => {
@@ -1011,21 +1018,39 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
10111018
(node) => node.type === WorkflowNodeType.CI || node.type === WorkflowNodeType.WEBHOOK,
10121019
)
10131020
if (_ciNode) {
1014-
// Need to add check for webhook if its source type is git
1015-
if (!_ciNode.isLinkedCI && !_ciNode.isLinkedCD) {
1021+
if (isBuildAndBranchTriggerAllowed(_ciNode)) {
10161022
appIds.push(wf.appId)
10171023
appNameMap.set(wf.appId, wf.name)
10181024
}
1025+
else {
1026+
skippedResources.push({
1027+
appId: wf.appId,
1028+
appName: wf.name,
1029+
statusText: SKIPPED_RESOURCES_STATUS_TEXT,
1030+
status: BulkResponseStatus.SKIP,
1031+
envId: +envId,
1032+
message: SKIPPED_RESOURCES_MESSAGE,
1033+
})
1034+
}
10191035
}
10201036
}
10211037
})
10221038

1023-
if (!appIds.length) {
1039+
if (!appIds.length && !skippedResources.length) {
10241040
toast.error('No valid application present')
10251041
return
10261042
}
1027-
10281043
setIsBranchChangeLoading(true)
1044+
1045+
if (!appIds.length) {
1046+
updateResponseListData(skippedResources)
1047+
setIsBranchChangeLoading(false)
1048+
setCDLoading(false)
1049+
setCILoading(false)
1050+
preventBodyScroll(true)
1051+
return
1052+
}
1053+
10291054
triggerBranchChange(appIds, +envId, value)
10301055
.then((response: any) => {
10311056
const _responseList = []
@@ -1039,10 +1064,10 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
10391064
message: res.message,
10401065
})
10411066
})
1042-
updateResponseListData(_responseList)
1067+
updateResponseListData([..._responseList, ...skippedResources])
10431068
setCDLoading(false)
10441069
setCILoading(false)
1045-
preventBodyScroll(false)
1070+
preventBodyScroll(true)
10461071
})
10471072
.catch((error) => {
10481073
showError(error)
@@ -1206,6 +1231,7 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
12061231
setIsBranchChangeLoading(false)
12071232
setShowBulkSourceChangeModal(false)
12081233
setResponseList([])
1234+
preventBodyScroll(false)
12091235
}
12101236

12111237
const onShowChangeSourceModal = () => {
@@ -1324,10 +1350,11 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
13241350
promiseList: any[],
13251351
triggeredAppList: { appId: number; envId?: number; appName: string }[],
13261352
type: WorkflowNodeType,
1353+
skippedResources: ResponseRowType[] = [],
13271354
): void => {
1355+
const _responseList = skippedResources
13281356
if (promiseList.length) {
13291357
Promise.allSettled(promiseList).then((responses: any) => {
1330-
const _responseList = []
13311358
responses.forEach((response, index) => {
13321359
if (response.status === 'fulfilled') {
13331360
const statusType = filterStatusType(
@@ -1399,10 +1426,15 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
13991426
})
14001427
} else {
14011428
setCDLoading(false)
1402-
setShowBulkCDModal(false)
14031429
setCILoading(false)
1404-
setShowBulkCIModal(false)
1405-
setResponseList([])
1430+
if (!skippedResources.length) {
1431+
setShowBulkCDModal(false)
1432+
setShowBulkCIModal(false)
1433+
setResponseList([])
1434+
}
1435+
else {
1436+
updateResponseListData(_responseList)
1437+
}
14061438
}
14071439
}
14081440

@@ -1426,18 +1458,29 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
14261458
ReactGA.event(ENV_TRIGGER_VIEW_GA_EVENTS.BulkCITriggered)
14271459
setCILoading(true)
14281460
let node
1461+
const skippedResources = []
14291462
const nodeList: NodeAttr[] = [],
14301463
triggeredAppList: { appId: number; appName: string }[] = []
14311464
for (const _wf of filteredWorkflows) {
14321465
if (_wf.isSelected && (!appsToRetry || appsToRetry[_wf.appId])) {
14331466
node = _wf.nodes.find((node) => {
1434-
return node.type === WorkflowNodeType.CI
1467+
return node.type === WorkflowNodeType.CI || node.type === WorkflowNodeType.WEBHOOK
14351468
})
1436-
// Maybe we dont need to push webhook as well
1437-
if (node && !node.isLinkedCI && !node.isLinkedCD) {
1469+
1470+
if (node && isBuildAndBranchTriggerAllowed(node)) {
14381471
triggeredAppList.push({ appId: _wf.appId, appName: _wf.name })
14391472
nodeList.push(node)
14401473
}
1474+
else if (node && !isBuildAndBranchTriggerAllowed(node)) {
1475+
// skipped can never be in appsToRetry
1476+
skippedResources.push({
1477+
appId: _wf.appId,
1478+
appName: _wf.name,
1479+
statusText: SKIPPED_RESOURCES_STATUS_TEXT,
1480+
status: BulkResponseStatus.SKIP,
1481+
message: SKIPPED_RESOURCES_MESSAGE,
1482+
})
1483+
}
14411484
}
14421485
}
14431486
const _CITriggerPromiseList = []
@@ -1479,13 +1522,15 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
14791522
}
14801523
_CITriggerPromiseList.push(triggerCINode(payload))
14811524
})
1482-
if (!_CITriggerPromiseList.length) {
1525+
1526+
if (!_CITriggerPromiseList.length && !skippedResources.length) {
14831527
toast.error('No valid CI pipeline found')
14841528
setCDLoading(false)
14851529
setCILoading(false)
14861530
return
14871531
}
1488-
handleBulkTrigger(_CITriggerPromiseList, triggeredAppList, WorkflowNodeType.CI)
1532+
1533+
handleBulkTrigger(_CITriggerPromiseList, triggeredAppList, WorkflowNodeType.CI, skippedResources)
14891534
}
14901535

14911536
// Would only set data no need to get data related to materials from it, we will get that in bulk trigger

src/components/ApplicationGroup/Details/TriggerView/SourceUpdateResponseModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react'
22
import { Progressing } from '@devtron-labs/devtron-fe-common-lib'
33
import { TriggerModalRow } from './TriggerModalTableRow'
4+
import { SourceUpdateResponseModalProps } from './types'
45

5-
export default function SourceUpdateResponseModal({ closePopup, responseList, isLoading }) {
6+
export default function SourceUpdateResponseModal({ closePopup, responseList, isLoading }: SourceUpdateResponseModalProps) {
67
const renderResponseBodySection = (): JSX.Element => {
78
if (isLoading) {
89
return <Progressing pageLoader />
@@ -25,6 +26,7 @@ export default function SourceUpdateResponseModal({ closePopup, responseList, is
2526
)
2627
}
2728

29+
// ASK: Why there is no retry button for failed request?
2830
const renderFooterSection = (): JSX.Element => {
2931
return (
3032
<div

src/components/ApplicationGroup/Details/TriggerView/TriggerModalTableRow.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ReactComponent as Error } from '../../../../assets/icons/ic-error-excla
44
import { ReactComponent as Success } from '../../../../assets/icons/appstatus/healthy.svg'
55
import { ReactComponent as Download } from '../../../../assets/icons/ic-arrow-line-down.svg'
66
import { ReactComponent as UnAuthorized } from '../../../../assets/icons/ic-locked.svg'
7+
import { ReactComponent as ICInfoFilled } from '../../../../assets/icons/ic-info-filled.svg'
78
import { Progressing } from '@devtron-labs/devtron-fe-common-lib'
89
import { BulkResponseStatus } from '../../Constants'
910
import { importComponentFromFELibrary } from '../../../common'
@@ -20,7 +21,9 @@ export function TriggerModalRow({ rowData, index, isVirtualEnv, envName, setDown
2021
}
2122

2223
const renderStatusIcon = (rowData: ResponseRowType): JSX.Element => {
23-
if (rowData.status === BulkResponseStatus.UNAUTHORIZE) {
24+
if (rowData.status === BulkResponseStatus.SKIP) {
25+
return <ICInfoFilled className="mr-8 icon-dim-18" />
26+
} else if (rowData.status === BulkResponseStatus.UNAUTHORIZE) {
2427
return <UnAuthorized className="mr-8 icon-dim-18 fcy-7" />
2528
} else if (rowData.status === BulkResponseStatus.PASS) {
2629
return <Success className="mr-8 icon-dim-18" />

src/components/ApplicationGroup/Details/TriggerView/TriggerResponseModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export default function TriggerResponseModal({
3939
)
4040
}
4141

42-
const handleRetryBuild = (): void => {
42+
const handleRetryBuild = (e: React.MouseEvent): void => {
43+
e.stopPropagation()
4344
const appsToRetry: Record<string, boolean> = {}
4445
for (const response of responseList) {
4546
if (response.status === BulkResponseStatus.FAIL) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Legacy components with no props so setting to any
2+
export interface BulkSourceChangeProps {
3+
closePopup: any
4+
responseList: any
5+
changeBranch: any
6+
loading: any
7+
selectedAppCount: any
8+
}
9+
10+
export interface SourceUpdateResponseModalProps {
11+
closePopup: any
12+
responseList: any
13+
isLoading: any
14+
}

0 commit comments

Comments
 (0)