Skip to content

Commit 9b3d43a

Browse files
committed
feat: Implement BulkDeployEmptyState component and integrate it into DeployImageContent for enhanced empty state handling
1 parent 5645325 commit 9b3d43a

File tree

6 files changed

+142
-95
lines changed

6 files changed

+142
-95
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {
2+
CommonNodeAttr,
3+
DeploymentNodeType,
4+
ErrorScreenManager,
5+
GenericEmptyState,
6+
TriggerBlockType,
7+
} from '@devtron-labs/devtron-fe-common-lib'
8+
9+
import emptyPreDeploy from '@Images/empty-pre-deploy.webp'
10+
import { BULK_CD_MESSAGING } from '@Components/ApplicationGroup/Constants'
11+
import { importComponentFromFELibrary } from '@Components/common'
12+
13+
import { BulkDeployEmptyStateProps } from './types'
14+
15+
const TriggerBlockEmptyState = importComponentFromFELibrary('TriggerBlockEmptyState', null, 'function')
16+
const MissingPluginBlockState = importComponentFromFELibrary('MissingPluginBlockState', null, 'function')
17+
18+
const BulkDeployEmptyState = ({
19+
selectedApp,
20+
stageType,
21+
appId,
22+
isTriggerBlockedDueToPlugin,
23+
handleClose,
24+
reloadMaterials,
25+
}: BulkDeployEmptyStateProps) => {
26+
if (TriggerBlockEmptyState && selectedApp.triggerBlockedInfo?.blockedBy === TriggerBlockType.MANDATORY_TAG) {
27+
return <TriggerBlockEmptyState stageType={stageType} appId={appId} />
28+
}
29+
30+
if (isTriggerBlockedDueToPlugin) {
31+
// It can't be CD
32+
const commonNodeAttrType: CommonNodeAttr['type'] = stageType === DeploymentNodeType.PRECD ? 'PRECD' : 'POSTCD'
33+
34+
return (
35+
<MissingPluginBlockState
36+
configurePluginURL={selectedApp.configurePluginURL}
37+
nodeType={commonNodeAttrType}
38+
/>
39+
)
40+
}
41+
42+
if (selectedApp.materialError) {
43+
return (
44+
<ErrorScreenManager
45+
code={selectedApp.materialError.code}
46+
reload={reloadMaterials}
47+
on404Redirect={handleClose}
48+
/>
49+
)
50+
}
51+
52+
return (
53+
<GenericEmptyState
54+
image={emptyPreDeploy}
55+
title={`${selectedApp?.appName} ${BULK_CD_MESSAGING[stageType].title}`}
56+
subTitle={BULK_CD_MESSAGING[stageType].subTitle}
57+
/>
58+
)
59+
}
60+
61+
export default BulkDeployEmptyState

src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
ButtonStyleType,
1111
CDMaterialResponseType,
1212
CDMaterialServiceEnum,
13-
CDMaterialSidebarType,
1413
CDMaterialType,
1514
DEPLOYMENT_WINDOW_TYPE,
1615
DeploymentNodeType,
@@ -65,7 +64,7 @@ import { getCDPipelineURL, importComponentFromFELibrary, useAppContext } from '@
6564
import { getModuleInfo } from '@Components/v2/devtronStackManager/DevtronStackManager.service'
6665

6766
import { getIsMaterialApproved } from '../cdMaterials.utils'
68-
import { FilterConditionViews } from '../types'
67+
import { INITIAL_DEPLOY_VIEW_STATE } from './constants'
6968
import DeployImageContent from './DeployImageContent'
7069
import DeployImageHeader from './DeployImageHeader'
7170
import { loadOlderImages } from './service'
@@ -369,31 +368,16 @@ const BulkDeployModal = ({ handleClose, stageType, workflows, isVirtualEnvironme
369368
},
370369
deploymentWindowMetadata: deploymentWindowMap[appId],
371370
areMaterialsLoading: false,
372-
deployViewState: {
373-
searchText: '',
374-
appliedSearchText: '',
375-
filterView: FilterConditionViews.ALL,
376-
showConfiguredFilters: false,
377-
currentSidebarTab: CDMaterialSidebarType.IMAGE,
378-
runtimeParamsErrorState: {
379-
isValid: true,
380-
cellError: {},
381-
},
382-
materialInEditModeMap: new Map(),
383-
showAppliedFilters: false,
384-
appliedFilterList: [],
385-
isLoadingOlderImages: false,
386-
showSearchBar: true,
387-
},
371+
deployViewState: structuredClone(INITIAL_DEPLOY_VIEW_STATE),
388372
warningMessage: updatedWarningMessage,
389373
materialError: null,
390374
}
391375
} else {
392376
bulkCDDetailsMap[appId] = {
393377
...baseBulkCDDetailMap[appId],
394-
materialResponse: null,
395-
deploymentWindowMetadata: null,
396-
deployViewState: null,
378+
materialResponse: {} as CDMaterialResponseType,
379+
deploymentWindowMetadata: {} as DeploymentWindowProfileMetaData,
380+
deployViewState: structuredClone(INITIAL_DEPLOY_VIEW_STATE),
397381
materialError: materialResponse.reason,
398382
areMaterialsLoading: false,
399383
}

src/components/app/details/triggerView/DeployImageModal/BulkTriggerSidebar.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,13 @@ const BulkTriggerSidebar = ({
144144
return <TriggerBlockedError stageType={stageType} />
145145
}
146146

147-
if (!!app.warningMessage && !app.showPluginWarning) {
147+
if ((!!app.warningMessage && !app.showPluginWarning) || app.materialError?.errors?.length) {
148148
return (
149149
<div className="flex left top dc__gap-4">
150-
<Icon name="ic-warning" color={null} size={14} />
151-
<span className="fw-4 fs-12 cy-7 dc__truncate">{app.warningMessage}</span>
150+
<Icon name="ic-warning-fill" color="R500" size={14} />
151+
<span className="fw-4 fs-12 cr-5 dc__truncate--clamp-2">
152+
{app.warningMessage || app.materialError?.errors?.[0]?.userMessage}
153+
</span>
152154
</div>
153155
)
154156
}
@@ -195,7 +197,7 @@ const BulkTriggerSidebar = ({
195197
isSearchable
196198
options={tagOptions}
197199
value={selectedTagOption}
198-
icon={<Icon name="ic-tag" size={16} color={null} />}
200+
icon={<Icon name="ic-tag" color={null} />}
199201
onChange={handleTagChange}
200202
isDisabled={false}
201203
// Not changing it for backward compatibility for automation

src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx

Lines changed: 40 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,9 @@ import {
77
ButtonVariantType,
88
CDMaterialSidebarType,
99
CDMaterialType,
10-
CommonNodeAttr,
1110
ComponentSizeType,
1211
DEPLOYMENT_WINDOW_TYPE,
1312
DeploymentNodeType,
14-
ErrorScreenManager,
15-
GenericEmptyState,
1613
getGitCommitInfo,
1714
getIsApprovalPolicyConfigured,
1815
getIsMaterialInfoAvailable,
@@ -32,13 +29,12 @@ import {
3229
useMainContext,
3330
} from '@devtron-labs/devtron-fe-common-lib'
3431

35-
import emptyPreDeploy from '@Images/empty-pre-deploy.webp'
36-
import { BULK_CD_MESSAGING } from '@Components/ApplicationGroup/Constants'
3732
import { importComponentFromFELibrary } from '@Components/common'
3833

3934
import { TriggerViewContext } from '../config'
4035
import { TRIGGER_VIEW_PARAMS } from '../Constants'
4136
import { FilterConditionViews, HandleRuntimeParamChange, TriggerViewContextType } from '../types'
37+
import BulkDeployEmptyState from './BulkDeployEmptyState'
4238
import BulkTriggerSidebar from './BulkTriggerSidebar'
4339
import ImageSelectionCTA from './ImageSelectionCTA'
4440
import MaterialListEmptyState from './MaterialListEmptyState'
@@ -64,8 +60,6 @@ const RuntimeParameters = importComponentFromFELibrary('RuntimeParameters', null
6460
const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function')
6561
const CDMaterialInfo = importComponentFromFELibrary('CDMaterialInfo')
6662
const ConfiguredFilters = importComponentFromFELibrary('ConfiguredFilters')
67-
const TriggerBlockEmptyState = importComponentFromFELibrary('TriggerBlockEmptyState', null, 'function')
68-
const MissingPluginBlockState = importComponentFromFELibrary('MissingPluginBlockState', null, 'function')
6963

7064
const DeployImageContent = ({
7165
appId,
@@ -99,6 +93,7 @@ const DeployImageContent = ({
9993
handleTagChange,
10094
changeApp,
10195
}: DeployImageContentProps) => {
96+
// WARNING: Pls try not to create a useState in this component, it is supposed to be a dumb component.
10297
const history = useHistory()
10398
const { isSuperAdmin } = useMainContext()
10499
const { onClickApprovalNode } = useContext<TriggerViewContextType>(TriggerViewContext)
@@ -144,9 +139,11 @@ const DeployImageContent = ({
144139
})
145140
const selectImageTitle = isRollbackTrigger ? 'Select from previously deployed images' : 'Select Image'
146141
const titleText = isApprovalConfigured && !isExceptionUser ? 'Approved images' : selectImageTitle
147-
const showActionBar = FilterActionBar && !isSearchApplied && !!resourceFilters?.length && !showConfiguredFilters
142+
const showActionBar = !!FilterActionBar && !isSearchApplied && !!resourceFilters?.length && !showConfiguredFilters
148143
const areNoMoreImagesPresent = materials.length >= materialResponse?.totalCount
149144

145+
const showFiltersView = !!(ConfiguredFilters && (showConfiguredFilters || showAppliedFilters))
146+
150147
const handleSidebarTabChange: RuntimeParamsSidebarProps['handleSidebarTabChange'] = (e) => {
151148
setDeployViewState((prevState) => ({
152149
...prevState,
@@ -348,7 +345,7 @@ const DeployImageContent = ({
348345
)
349346
}
350347

351-
if (isPreOrPostCD) {
348+
if (isPreOrPostCD && !showFiltersView) {
352349
return (
353350
<RuntimeParamsSidebar
354351
areTabsDisabled={false}
@@ -363,16 +360,26 @@ const DeployImageContent = ({
363360
return null
364361
}
365362

363+
const renderConfiguredFilters = () => (
364+
<ConfiguredFilters
365+
isFromBulkCD={isBulkTrigger}
366+
resourceFilters={showConfiguredFilters ? resourceFilters : appliedFilterList}
367+
handleDisableFiltersView={showConfiguredFilters ? handleExitFiltersView : handleDisableAppliedFiltersView}
368+
envName={envName}
369+
closeModal={handleClose}
370+
/>
371+
)
372+
366373
const renderGitMaterialInfo = (materialData: CDMaterialType) => (
367374
<>
368375
{materialData.materialInfo.map((mat: MaterialInfo, index) => {
369376
const _gitCommit = getGitCommitInfo(mat)
370377

371378
if (
379+
CDMaterialInfo &&
372380
(materialData.appliedFilters?.length > 0 ||
373381
materialData.deploymentBlockedState?.isBlocked ||
374-
materialData.deploymentWindowArtifactMetadata?.type) &&
375-
CDMaterialInfo
382+
materialData.deploymentWindowArtifactMetadata?.type)
376383
) {
377384
return (
378385
<CDMaterialInfo
@@ -508,47 +515,12 @@ const DeployImageContent = ({
508515
)
509516
})
510517

511-
const renderEmptyView = (): JSX.Element => {
512-
const selectedApp = appInfoMap[+appId]
513-
514-
if (selectedApp.triggerBlockedInfo?.blockedBy === TriggerBlockType.MANDATORY_TAG) {
515-
return <TriggerBlockEmptyState stageType={stageType} appId={appId} />
516-
}
517-
518-
if (isTriggerBlockedDueToPlugin) {
519-
// It can't be CD
520-
const commonNodeAttrType: CommonNodeAttr['type'] =
521-
stageType === DeploymentNodeType.PRECD ? 'PRECD' : 'POSTCD'
522-
523-
return (
524-
<MissingPluginBlockState
525-
configurePluginURL={selectedApp?.configurePluginURL}
526-
nodeType={commonNodeAttrType}
527-
/>
528-
)
529-
}
530-
531-
if (selectedApp.materialError) {
532-
return (
533-
<ErrorScreenManager
534-
code={selectedApp.materialError.code}
535-
reload={reloadMaterials}
536-
on404Redirect={handleClose}
537-
/>
538-
)
539-
}
540-
541-
return (
542-
<GenericEmptyState
543-
image={emptyPreDeploy}
544-
title={`${selectedApp?.appName} ${BULK_CD_MESSAGING[stageType].title}`}
545-
subTitle={BULK_CD_MESSAGING[stageType].subTitle}
546-
/>
547-
)
548-
}
549-
550518
const renderContent = () => {
551519
if (isBulkTrigger) {
520+
if (showFiltersView) {
521+
return renderConfiguredFilters()
522+
}
523+
552524
const { areMaterialsLoading, triggerBlockedInfo, materialError } = appInfoMap[+appId] || {}
553525
if (currentSidebarTab === CDMaterialSidebarType.IMAGE && areMaterialsLoading) {
554526
return <MaterialListSkeleton />
@@ -562,7 +534,16 @@ const DeployImageContent = ({
562534
materialError ||
563535
selectedApp?.stageNotAvailable
564536
) {
565-
return renderEmptyView()
537+
return (
538+
<BulkDeployEmptyState
539+
selectedApp={selectedApp}
540+
stageType={stageType}
541+
appId={appId}
542+
isTriggerBlockedDueToPlugin={isTriggerBlockedDueToPlugin}
543+
handleClose={handleClose}
544+
reloadMaterials={reloadMaterials}
545+
/>
546+
)
566547
}
567548
}
568549

@@ -587,7 +568,7 @@ const DeployImageContent = ({
587568
<span className="flex dc__align-start">{titleText}</span>
588569
)}
589570

590-
<span className="flexbox dc__align-items-center h-32 dc__gap-4">
571+
<div className="flexbox dc__align-items-center h-32 dc__gap-4">
591572
{showSearchBar || isSearchApplied ? (
592573
renderSearch()
593574
) : (
@@ -612,7 +593,7 @@ const DeployImageContent = ({
612593
showAriaLabelInTippy={false}
613594
size={ComponentSizeType.small}
614595
/>
615-
</span>
596+
</div>
616597
</div>
617598

618599
{materialList.length === 0 ? (
@@ -628,7 +609,6 @@ const DeployImageContent = ({
628609
isConsumedImagePresent={consumedImage.length > 0}
629610
envName={envName}
630611
materialResponse={materialResponse}
631-
// TODO: Move to util and remove prop
632612
isExceptionUser={isExceptionUser}
633613
isLoadingMore={isLoadingOlderImages}
634614
viewAllImages={viewAllImages}
@@ -672,34 +652,25 @@ const DeployImageContent = ({
672652
)
673653
}
674654

675-
if (ConfiguredFilters && (showConfiguredFilters || showAppliedFilters)) {
676-
return (
677-
<ConfiguredFilters
678-
isFromBulkCD={isBulkTrigger}
679-
resourceFilters={showConfiguredFilters ? resourceFilters : appliedFilterList}
680-
handleDisableFiltersView={
681-
showConfiguredFilters ? handleExitFiltersView : handleDisableAppliedFiltersView
682-
}
683-
envName={envName}
684-
closeModal={handleClose}
685-
/>
686-
)
655+
if (showFiltersView && !isBulkTrigger) {
656+
return renderConfiguredFilters()
687657
}
688658

689659
return (
690660
<>
691-
{isApprovalConfigured &&
661+
{!showFiltersView &&
662+
isApprovalConfigured &&
692663
!isExceptionUser &&
693664
ApprovedImagesMessage &&
694665
(isRollbackTrigger || materials.length - Number(isConsumedImageAvailable) > 0) && (
695666
<InfoBlock
696667
borderConfig={{ top: false }}
697668
borderRadiusConfig={{ top: false, bottom: false, left: false, right: false }}
698-
// TODO: Look if need to show this in bulk?
699669
description={<ApprovedImagesMessage viewAllImages={viewAllImages} />}
700670
/>
701671
)}
702-
{!isBulkTrigger &&
672+
{!showFiltersView &&
673+
!isBulkTrigger &&
703674
MaintenanceWindowInfoBar &&
704675
deploymentWindowMetadata.type === DEPLOYMENT_WINDOW_TYPE.MAINTENANCE &&
705676
deploymentWindowMetadata.isActive && (
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { CDMaterialSidebarType } from '@devtron-labs/devtron-fe-common-lib'
2+
3+
import { FilterConditionViews } from '../types'
4+
import { DeployViewStateType } from './types'
5+
6+
export const INITIAL_DEPLOY_VIEW_STATE: DeployViewStateType = {
7+
searchText: '',
8+
appliedSearchText: '',
9+
filterView: FilterConditionViews.ALL,
10+
showConfiguredFilters: false,
11+
currentSidebarTab: CDMaterialSidebarType.IMAGE,
12+
runtimeParamsErrorState: {
13+
isValid: true,
14+
cellError: {},
15+
},
16+
materialInEditModeMap: new Map(),
17+
showAppliedFilters: false,
18+
appliedFilterList: [],
19+
isLoadingOlderImages: false,
20+
showSearchBar: true,
21+
}

0 commit comments

Comments
 (0)