Skip to content

Commit b3699cd

Browse files
committed
feat: enhance BulkBuildImageModal and GitInfoMaterial for improved error handling and state management
1 parent a13f089 commit b3699cd

File tree

7 files changed

+238
-132
lines changed

7 files changed

+238
-132
lines changed

src/components/app/details/triggerView/BuildImageModal/BuildImageModal.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const BuildImageModal = ({
8787
const workflowId = selectedWorkflow?.id
8888
const ciNode = selectedWorkflow?.nodes.find((node) => node.type === CIPipelineNodeType.CI && node.id === ciNodeId)
8989
const appId = appIdProp || selectedWorkflow?.appId
90-
const filteredCIPipelines = filteredCIPipelinesProp || filteredCIPipelineMap?.get(String(appId)) || []
90+
const filteredCIPipelines = filteredCIPipelinesProp || filteredCIPipelineMap?.get(appId) || []
9191

9292
const selectedCIPipeline = filteredCIPipelines.find((_ci) => _ci.id === +ciNodeId)
9393

@@ -387,7 +387,6 @@ const BuildImageModal = ({
387387
>
388388
{!showContentLoader && !screenErrorData && (
389389
<GitInfoMaterial
390-
appId={appId}
391390
workflowId={selectedWorkflow?.id}
392391
node={ciNode}
393392
isJobView={isJobView}

src/components/app/details/triggerView/BuildImageModal/BulkBuildImageModal.tsx

Lines changed: 41 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useRef, useState } from 'react'
1+
import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
22

33
import {
44
API_STATUS_CODES,
@@ -31,8 +31,6 @@ import {
3131
BULK_CI_RESPONSE_STATUS_TEXT,
3232
BulkResponseStatus,
3333
ENV_TRIGGER_VIEW_GA_EVENTS,
34-
SKIPPED_RESOURCES_MESSAGE,
35-
SKIPPED_RESOURCES_STATUS_TEXT,
3634
} from '@Components/ApplicationGroup/Constants'
3735
import TriggerResponseModalBody, {
3836
TriggerResponseModalFooter,
@@ -44,14 +42,13 @@ import { getModuleConfigured } from '../../appDetails/appDetails.service'
4442
import { IGNORE_CACHE_INFO } from '../Constants'
4543
import BuildImageHeader from './BuildImageHeader'
4644
import GitInfoMaterial from './GitInfoMaterial'
47-
import { getCIMaterials, triggerBuild } from './service'
45+
import { getCIMaterials } from './service'
4846
import { BulkBuildImageModalProps, GitInfoMaterialProps } from './types'
4947
import {
5048
getBulkCIDataPromiseGetterList,
5149
getBulkCIErrorMessage,
52-
getBulkCIWarningMessage,
53-
getCanNodeHaveMaterial,
54-
getTriggerBuildPayload,
50+
getTriggerCIPromiseListAndSkippedResources,
51+
parseBulkCIResponseIntoBulkCIDetail,
5552
} from './utils'
5653

5754
const validateRuntimeParameters = importComponentFromFELibrary(
@@ -86,6 +83,8 @@ const BulkBuildImageModal = ({
8683
workflow.nodes.some((node) => node.type === WorkflowNodeType.CI || node.type === WorkflowNodeType.WEBHOOK),
8784
)
8885

86+
const [numberOfAppsLoading, setNumberOfAppsLoading] = useState<number>(0)
87+
8988
// Returns map of appId to BulkCIDetailType
9089
const getInitialAppList = async (appId?: number): Promise<Record<number, BulkCIDetailType>> => {
9190
const validWorkflows = selectedWorkflows.filter((workflow) => !appId || workflow.appId === appId)
@@ -95,6 +94,8 @@ const BulkBuildImageModal = ({
9594
initialDataAbortControllerRef,
9695
)
9796

97+
setNumberOfAppsLoading(validWorkflows.length)
98+
9899
if (ciMaterialPromiseList.length === 0) {
99100
return []
100101
}
@@ -103,60 +104,23 @@ const BulkBuildImageModal = ({
103104
await ApiQueuingWithBatch<Awaited<ReturnType<typeof getCIMaterials>>>(ciMaterialPromiseList)
104105
const runtimeParamsList = await ApiQueuingWithBatch<RuntimePluginVariables[]>(runtimeParamsPromiseList)
105106

106-
return validWorkflows.reduce<Record<number, BulkCIDetailType>>((acc, workflow, index) => {
107-
const node = workflow.nodes.find(
108-
(currentNode) =>
109-
currentNode.type === WorkflowNodeType.CI || currentNode.type === WorkflowNodeType.WEBHOOK,
110-
)
111-
112-
if (!node) {
113-
return acc
114-
}
107+
setNumberOfAppsLoading(0)
115108

116-
const currentMaterial =
117-
(ciMaterialList[index].status === PromiseAllStatusType.FULFILLED ? ciMaterialList[index].value : []) ||
118-
[]
119-
const runtimeParams =
120-
runtimeParamsList[index].status === PromiseAllStatusType.FULFILLED ? runtimeParamsList[index].value : []
121-
122-
acc[workflow.appId] = {
123-
workflowId: workflow.id,
124-
appId: workflow.appId,
125-
name: workflow.name,
126-
node,
127-
material: currentMaterial,
128-
materialInitialError:
129-
ciMaterialList[index].status === PromiseAllStatusType.REJECTED
130-
? ciMaterialList[index].reason
131-
: null,
132-
runtimeParams: runtimeParams || [],
133-
runtimeParamsInitialError:
134-
runtimeParamsList[index].status === PromiseAllStatusType.REJECTED
135-
? runtimeParamsList[index].reason
136-
: null,
137-
runtimeParamsErrorState: {
138-
isValid: runtimeParamsList[index].status !== PromiseAllStatusType.REJECTED,
139-
cellError: {},
140-
},
141-
warningMessage: getBulkCIWarningMessage(node),
142-
errorMessage: getBulkCIErrorMessage(
143-
workflow.appId,
144-
node,
145-
filteredCIPipelineMap.get(String(workflow.appId)),
146-
currentMaterial,
147-
),
148-
filteredCIPipelines: filteredCIPipelineMap.get(String(workflow.appId)),
149-
ignoreCache: false,
150-
ciConfiguredGitMaterialId: workflow.ciConfiguredGitMaterialId,
151-
}
109+
const bulkCIDetailsMap = parseBulkCIResponseIntoBulkCIDetail({
110+
ciMaterialList,
111+
runtimeParamsList,
112+
validWorkflows,
113+
filteredCIPipelineMap,
114+
})
152115

153-
return acc
154-
}, {})
116+
return bulkCIDetailsMap
155117
}
156118

157-
const [isLoadingAppInfoMap, appInfoMapRes, , , setAppInfoMapRes] = useAsync(getInitialAppList)
119+
const [isLoadingAppInfoMap, appInfoMapRes, , , setAppInfoMapResWithoutType] = useAsync(getInitialAppList)
158120
const [isLoadingSingleAppInfoMap, setIsLoadingSingleAppInfoMap] = useState<boolean>(false)
159121

122+
const setAppInfoMapRes: Dispatch<SetStateAction<Record<number, BulkCIDetailType>>> = setAppInfoMapResWithoutType
123+
160124
const appInfoMap = appInfoMapRes || {}
161125

162126
const sortedAppList = Object.values(appInfoMap).sort((a, b) => stringComparatorBySortOrder(a.name, b.name))
@@ -168,9 +132,10 @@ const BulkBuildImageModal = ({
168132
setIsLoadingSingleAppInfoMap(true)
169133
initialDataAbortControllerRef.current.abort()
170134
initialDataAbortControllerRef.current = new AbortController()
135+
// Will also handle error state
171136
const currentAppInfo = await getInitialAppList(selectedAppId)
172137
setAppInfoMapRes((prevAppInfoMapRes) => {
173-
const updatedAppInfoMap = { ...prevAppInfoMapRes }
138+
const updatedAppInfoMap = structuredClone(prevAppInfoMapRes)
174139
updatedAppInfoMap[selectedAppId] = currentAppInfo[selectedAppId]
175140
return updatedAppInfoMap
176141
})
@@ -230,17 +195,6 @@ const BulkBuildImageModal = ({
230195
return true
231196
}
232197

233-
const getPayloadFromAppDetails = (appDetails: BulkCIDetailType) =>
234-
getTriggerBuildPayload({
235-
materialList: appDetails.material,
236-
ciConfiguredGitMaterialId: appDetails.ciConfiguredGitMaterialId,
237-
runtimeParams: appDetails.runtimeParams,
238-
invalidateCache: appDetails.ignoreCache,
239-
isJobCI: appDetails.node?.isJobCI,
240-
ciNodeId: +(appDetails.node?.id || 0),
241-
selectedEnv: null,
242-
})
243-
244198
const getResponseStatusFromCode = (errorCode: number): BulkResponseStatus => {
245199
switch (errorCode) {
246200
case API_STATUS_CODES.EXPECTATION_FAILED:
@@ -262,45 +216,11 @@ const BulkBuildImageModal = ({
262216
handleAnalyticsEvent(ENV_TRIGGER_VIEW_GA_EVENTS.BulkCITriggered)
263217
setIsBuildTriggerLoading(true)
264218

265-
const newResourceList: ResponseRowType[] = []
266-
const appsToTrigger = sortedAppList.filter((appDetails) => {
267-
if (appsToRetry && !appsToRetry[appDetails.appId]) {
268-
return false
269-
}
270-
271-
if (!getCanNodeHaveMaterial(appDetails.node)) {
272-
newResourceList.push({
273-
appId: appDetails.appId,
274-
appName: appDetails.name,
275-
statusText: SKIPPED_RESOURCES_STATUS_TEXT,
276-
status: BulkResponseStatus.SKIP,
277-
message: SKIPPED_RESOURCES_MESSAGE,
278-
})
279-
}
280-
281-
const payload = getPayloadFromAppDetails(appDetails)
282-
283-
if (typeof payload === 'string') {
284-
newResourceList.push({
285-
appId: appDetails.appId,
286-
appName: appDetails.name,
287-
statusText: SKIPPED_RESOURCES_STATUS_TEXT,
288-
status: BulkResponseStatus.SKIP,
289-
message: payload,
290-
})
291-
}
292-
293-
return getCanNodeHaveMaterial(appDetails.node) && typeof payload !== 'string'
294-
})
295-
296-
const promiseList = appsToTrigger.map((appDetails) => {
297-
const payload = getPayloadFromAppDetails(appDetails) as Exclude<
298-
ReturnType<typeof getTriggerBuildPayload>,
299-
string
300-
>
301-
302-
return () => triggerBuild({ payload })
303-
})
219+
const {
220+
promiseList,
221+
appsToTrigger,
222+
skippedResourceList: newResourceList,
223+
} = getTriggerCIPromiseListAndSkippedResources(sortedAppList, appsToRetry)
304224

305225
if (!promiseList.length) {
306226
ToastManager.showToast({
@@ -351,10 +271,11 @@ const BulkBuildImageModal = ({
351271
await handleTriggerBuild()
352272
}
353273

354-
// TODO: Need to understand this
355274
const isStartBuildDisabled = (): boolean =>
356275
sortedAppList.some(
357276
(app) =>
277+
!!app.runtimeParamsInitialError ||
278+
!!app.materialInitialError ||
358279
app.node.isTriggerBlocked ||
359280
(app.errorMessage &&
360281
(app.errorMessage !== SOURCE_NOT_CONFIGURED ||
@@ -419,11 +340,17 @@ const BulkBuildImageModal = ({
419340

420341
const setCurrentAppMaterialList: GitInfoMaterialProps['setMaterialList'] = (getUpdatedMaterialList) => {
421342
setAppInfoMapRes((prevAppInfoMapRes) => {
422-
const updatedAppInfoMap = { ...prevAppInfoMapRes }
343+
const updatedAppInfoMap = structuredClone(prevAppInfoMapRes)
423344
const currentApp = updatedAppInfoMap[selectedAppId]
424345

425346
if (currentApp) {
426347
currentApp.material = getUpdatedMaterialList(currentApp.material)
348+
currentApp.errorMessage = getBulkCIErrorMessage(
349+
currentApp.appId,
350+
currentApp.node,
351+
currentApp.filteredCIPipelines,
352+
currentApp.material,
353+
)
427354
}
428355

429356
return updatedAppInfoMap
@@ -432,7 +359,7 @@ const BulkBuildImageModal = ({
432359

433360
const handleRuntimeParamChange: GitInfoMaterialProps['handleRuntimeParamChange'] = (updatedRuntimeParams) => {
434361
setAppInfoMapRes((prevAppInfoMapRes) => {
435-
const updatedAppInfoMap = { ...prevAppInfoMapRes }
362+
const updatedAppInfoMap = structuredClone(prevAppInfoMapRes)
436363
const currentApp = updatedAppInfoMap[selectedAppId]
437364
if (currentApp) {
438365
currentApp.runtimeParams = updatedRuntimeParams
@@ -443,7 +370,7 @@ const BulkBuildImageModal = ({
443370

444371
const handleRuntimeParamError: GitInfoMaterialProps['handleRuntimeParamError'] = (errorState) => {
445372
setAppInfoMapRes((prevAppInfoMapRes) => {
446-
const updatedAppInfoMap = { ...prevAppInfoMapRes }
373+
const updatedAppInfoMap = structuredClone(prevAppInfoMapRes)
447374
const currentApp = updatedAppInfoMap[selectedAppId]
448375
if (currentApp) {
449376
currentApp.runtimeParamsErrorState = errorState
@@ -454,8 +381,9 @@ const BulkBuildImageModal = ({
454381

455382
const toggleSelectedAppIgnoreCache = () => {
456383
setAppInfoMapRes((prevAppInfoMapRes) => {
457-
const updatedAppInfoMap = { ...prevAppInfoMapRes }
384+
const updatedAppInfoMap = structuredClone(prevAppInfoMapRes)
458385
const currentApp = updatedAppInfoMap[selectedAppId]
386+
459387
if (currentApp) {
460388
currentApp.ignoreCache = !currentApp.ignoreCache
461389
}
@@ -470,8 +398,8 @@ const BulkBuildImageModal = ({
470398
const renderContent = () => {
471399
if (isLoadingAppInfoMap || isBuildTriggerLoading) {
472400
const message = isBuildTriggerLoading
473-
? BULK_CI_BUILD_STATUS(selectedWorkflows.length)
474-
: BULK_CI_MATERIAL_STATUS(selectedWorkflows.length)
401+
? BULK_CI_BUILD_STATUS(numberOfAppsLoading)
402+
: BULK_CI_MATERIAL_STATUS(numberOfAppsLoading)
475403

476404
return <GenericEmptyState {...message} SvgImage={MechanicalOperation} contentClassName="text-center" />
477405
}
@@ -485,7 +413,6 @@ const BulkBuildImageModal = ({
485413

486414
return (
487415
<GitInfoMaterial
488-
key={selectedAppId}
489416
workflowId={appData.workflowId}
490417
appId={selectedAppId}
491418
node={appData.node}

src/components/app/details/triggerView/BuildImageModal/GitInfoMaterial.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ComponentSizeType,
1212
ConditionalWrap,
1313
createGitCommitUrl,
14+
ErrorScreenManager,
1415
GenericEmptyState,
1516
getHandleOpenURL,
1617
handleUTCTime,
@@ -66,15 +67,30 @@ const GitInfoMaterial = ({
6667
handleReloadWithWorkflows,
6768
isBulkTrigger = false,
6869
appList,
69-
handleAppChange,
70+
handleAppChange: handleAppChangeProp,
7071
isBlobStorageConfigured,
7172
toggleSelectedAppIgnoreCache,
7273
}: GitInfoMaterialProps) => {
73-
// TODO: Discuss if we can remove key in case of bulk? Maybe one useEffect?
7474
const [currentSidebarTab, setCurrentSidebarTab] = useState<CIMaterialSidebarType>(CIMaterialSidebarType.CODE_SOURCE)
7575
const [showRegexBranchChangeModal, setShowRegexBranchChangeModal] = useState<boolean>(
7676
getIsRegexBranchNotAvailable(selectedCIPipeline, materialList),
7777
)
78+
79+
const handleAppChange = (newAppId: number) => {
80+
handleAppChangeProp?.(newAppId)
81+
82+
// appId will only change in case of bulk trigger
83+
setCurrentSidebarTab(CIMaterialSidebarType.CODE_SOURCE)
84+
if (appList) {
85+
const targetApp = appList.find((app) => app.appId === newAppId)
86+
const { filteredCIPipelines, node: targetAppNode, material } = targetApp
87+
const newSelectedCIPipeline = targetAppNode
88+
? filteredCIPipelines.find((pipeline) => +pipeline.id === +targetAppNode.id)
89+
: null
90+
setShowRegexBranchChangeModal(getIsRegexBranchNotAvailable(newSelectedCIPipeline, material))
91+
}
92+
}
93+
7894
const nodeId = node?.id
7995
const isCITriggerBlocked = node?.isTriggerBlocked
8096

@@ -621,6 +637,15 @@ const GitInfoMaterial = ({
621637
/>
622638
)
623639
}
640+
641+
if (selectedApp.materialInitialError || selectedApp.runtimeParamsInitialError) {
642+
return (
643+
<ErrorScreenManager
644+
code={selectedApp.materialInitialError?.code || selectedApp.runtimeParamsInitialError?.code}
645+
reload={reloadCompleteMaterialList}
646+
/>
647+
)
648+
}
624649
}
625650

626651
const areCommitsPresent = selectedMaterial.history?.length > 0

src/components/app/details/triggerView/BuildImageModal/TriggerBuildSidebar.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ const TriggerBuildSidebar = ({
7171
}
7272
}
7373

74+
const getErrorMessageFromAppDetails = (appDetails: (typeof appList)[number]): string | null => {
75+
const materialListError = appDetails.materialInitialError
76+
? appDetails.materialInitialError.errors?.[0].userMessage || 'Error fetching material list'
77+
: null
78+
79+
const runtimeParamsInitialError = appDetails.runtimeParamsInitialError
80+
? appDetails.runtimeParamsInitialError.errors?.[0].userMessage || 'Error fetching runtime parameters'
81+
: null
82+
83+
const runtimeParamsDataError = !appDetails.runtimeParamsErrorState?.isValid
84+
? 'Invalid runtime parameters'
85+
: null
86+
87+
return appDetails.errorMessage || materialListError || runtimeParamsInitialError || runtimeParamsDataError
88+
}
89+
7490
const renderAppName = (appDetails: (typeof appList)[number]): JSX.Element | null => (
7591
<div
7692
role="button"
@@ -85,10 +101,10 @@ const TriggerBuildSidebar = ({
85101
{appDetails.warningMessage}
86102
</span>
87103
)}
88-
{appDetails.appId !== appId && appDetails.errorMessage && (
104+
{appDetails.appId !== appId && !!getErrorMessageFromAppDetails(appDetails) && (
89105
<span className="flexbox cr-5 fw-4 fs-12 dc__gap-4">
90106
<Icon name="ic-error" size={20} color={null} />
91-
<span className="dc__block dc__word-break lh-20">{appDetails.errorMessage}</span>
107+
<span className="dc__block dc__word-break lh-20">{getErrorMessageFromAppDetails(appDetails)}</span>
92108
</span>
93109
)}
94110
{appDetails.node?.pluginBlockState &&

0 commit comments

Comments
 (0)