From c2cc74393279245099c6d1875f11ed861b68b896 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Fri, 8 Aug 2025 12:20:47 +0530 Subject: [PATCH 1/9] refactor: clean up unused code and improve null checks in TriggerView components --- .../TriggerView/TriggerResponseModal.tsx | 2 +- .../triggerView/DeployImageModal/utils.tsx | 2 -- .../app/details/triggerView/TriggerView.tsx | 20 +++++++++---------- .../details/triggerView/TriggerView.utils.tsx | 3 --- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/components/ApplicationGroup/Details/TriggerView/TriggerResponseModal.tsx b/src/components/ApplicationGroup/Details/TriggerView/TriggerResponseModal.tsx index fef6990771..fcb40f8a72 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/TriggerResponseModal.tsx +++ b/src/components/ApplicationGroup/Details/TriggerView/TriggerResponseModal.tsx @@ -81,7 +81,7 @@ const TriggerResponseModalBody = ({ responseList, isLoading, isVirtualEnv }: Tri return } return ( -
+
{ !mat.userApprovalMetadata || mat.userApprovalMetadata.approvalRuntimeState !== ApprovalRuntimeStateType.approved ) { - // TODO: Check if this is needed - // mat.isSelected = false consumedImage.push(mat) } else { approvedImages.push(mat) diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx index 0496bd9976..d6c34ab288 100644 --- a/src/components/app/details/triggerView/TriggerView.tsx +++ b/src/components/app/details/triggerView/TriggerView.tsx @@ -116,9 +116,7 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE) ) { const cdNode: CommonNodeAttr = getSelectedNodeFromWorkflows(workflows, location.search) - if (!cdNode.id) { - return null - } + const materialType = location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) ? MATERIAL_TYPE.inputMaterialList : MATERIAL_TYPE.rollbackMaterialList @@ -128,10 +126,10 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { const doesWorkflowContainsWebhook = selectedCINode?.type === 'WEBHOOK' const configurePluginURL = getCDPipelineURL( appId, - selectedWorkflow.id, + selectedWorkflow?.id || '0', doesWorkflowContainsWebhook ? '0' : selectedCINode?.id, doesWorkflowContainsWebhook, - cdNode.id, + cdNode.id || '0', true, ) @@ -139,19 +137,19 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { diff --git a/src/components/app/details/triggerView/TriggerView.utils.tsx b/src/components/app/details/triggerView/TriggerView.utils.tsx index e552a172dd..ba533bb7ef 100644 --- a/src/components/app/details/triggerView/TriggerView.utils.tsx +++ b/src/components/app/details/triggerView/TriggerView.utils.tsx @@ -22,7 +22,6 @@ import { DeploymentNodeType, DeploymentWithConfigType, handleAnalyticsEvent, - showError, WorkflowType, } from '@devtron-labs/devtron-fe-common-lib' @@ -216,7 +215,6 @@ export const getSelectedNodeFromWorkflows = (workflows: WorkflowType[], search: const { cdNodeId, nodeType } = getNodeIdAndTypeFromSearch(search) if (!cdNodeId) { - showError('Invalid node id') return {} as CommonNodeAttr } @@ -228,7 +226,6 @@ export const getSelectedNodeFromWorkflows = (workflows: WorkflowType[], search: return foundNode } - showError('Invalid node id') return {} as CommonNodeAttr } From 0ffe5d3bc0d0b36fa8eb9989f02f130f9cd9d6ac Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Fri, 8 Aug 2025 15:27:13 +0530 Subject: [PATCH 2/9] feat: common out render cd material from trigger view and env trigger view --- .eslintignore | 2 - .../Details/TriggerView/EnvTriggerView.tsx | 96 ++----------- .../Details/TriggerView/types.ts | 11 -- .../Details/TriggerView/utils.ts | 41 +----- .../app/details/triggerView/CDMaterial.tsx | 66 +++++++++ .../app/details/triggerView/TriggerView.tsx | 82 ++---------- .../details/triggerView/TriggerView.utils.tsx | 16 +-- .../app/details/triggerView/types.ts | 126 +----------------- src/components/app/types.ts | 3 +- 9 files changed, 102 insertions(+), 341 deletions(-) create mode 100644 src/components/app/details/triggerView/CDMaterial.tsx diff --git a/.eslintignore b/.eslintignore index e82439818d..75f0ff3a09 100755 --- a/.eslintignore +++ b/.eslintignore @@ -103,8 +103,6 @@ src/components/app/details/testViewer/TestRunList.tsx src/components/app/details/triggerView/EmptyStateCIMaterial.tsx src/components/app/details/triggerView/MaterialSource.tsx src/components/app/details/triggerView/__tests__/triggerview.test.tsx -src/components/app/details/triggerView/cdMaterial.tsx -src/components/app/details/triggerView/ciMaterial.tsx src/components/app/details/triggerView/ciWebhook.service.ts src/components/app/details/triggerView/config.ts src/components/app/details/triggerView/workflow.service.ts diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx index bcd169b71b..b1bacb0753 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx +++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useEffect, useRef, useState } from 'react' +import React, { useEffect, useState } from 'react' import { Prompt, Route, Switch, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom' import Tippy from '@tippyjs/react' @@ -29,7 +29,6 @@ import { DEFAULT_ROUTE_PROMPT_MESSAGE, DeploymentNodeType, ErrorScreenManager, - handleAnalyticsEvent, PopupMenu, Progressing, ServerErrors, @@ -43,7 +42,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { BuildImageModal, BulkBuildImageModal } from '@Components/app/details/triggerView/BuildImageModal' -import { BulkDeployModal, DeployImageModal } from '@Components/app/details/triggerView/DeployImageModal' +import { BulkDeployModal } from '@Components/app/details/triggerView/DeployImageModal' import { shouldRenderWebhookAddImageModal } from '@Components/app/details/triggerView/TriggerView.utils' import { getExternalCIConfig } from '@Components/ciPipeline/Webhook/webhook.service' @@ -58,7 +57,7 @@ import { TRIGGER_VIEW_PARAMS } from '../../../app/details/triggerView/Constants' import { CIMaterialRouterProps, MATERIAL_TYPE } from '../../../app/details/triggerView/types' import { Workflow } from '../../../app/details/triggerView/workflow/Workflow' import { triggerBranchChange } from '../../../app/service' -import { getCDPipelineURL, importComponentFromFELibrary, sortObjectArrayAlphabetically } from '../../../common' +import { importComponentFromFELibrary, sortObjectArrayAlphabetically } from '../../../common' import { getModuleInfo } from '../../../v2/devtronStackManager/DevtronStackManager.service' import { getWorkflows, getWorkflowStatus } from '../../AppGroup.service' import { @@ -70,16 +69,15 @@ import { import { processWorkflowStatuses } from '../../AppGroup.utils' import { BulkResponseStatus, - ENV_TRIGGER_VIEW_GA_EVENTS, GetBranchChangeStatus, SKIPPED_RESOURCES_MESSAGE, SKIPPED_RESOURCES_STATUS_TEXT, } from '../../Constants' import BulkSourceChange from './BulkSourceChange' -import { RenderCDMaterialContentProps } from './types' -import { getSelectedNodeAndAppId, getSelectedNodeAndMeta } from './utils' +import { getSelectedNodeAndAppId } from './utils' import './EnvTriggerView.scss' +import CDMaterial from '@Components/app/details/triggerView/CDMaterial' const ApprovalMaterialModal = importComponentFromFELibrary('ApprovalMaterialModal') const processDeploymentWindowStateAppGroup = importComponentFromFELibrary( @@ -529,86 +527,10 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT ) } - const renderCDMaterialContent = ({ - node, - appId, - workflowId, - selectedAppName, - doesWorkflowContainsWebhook, - ciNodeId, - }: RenderCDMaterialContentProps) => { - const configurePluginURL = getCDPipelineURL( - String(appId), - workflowId, - doesWorkflowContainsWebhook ? '0' : ciNodeId, - doesWorkflowContainsWebhook, - node?.id, - true, - ) - - const cdMaterialType = location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) - ? MATERIAL_TYPE.inputMaterialList - : MATERIAL_TYPE.rollbackMaterialList - - return ( - - ) - } - - const renderCDMaterial = (): JSX.Element | null => { - if ( - location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) || - location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE) - ) { - const { node, appId, workflowId, appName, selectedCINode } = getSelectedNodeAndMeta( - filteredWorkflows, - location.search, - ) - - if (!node?.id) { - return null - } - - return renderCDMaterialContent({ - node, - appId, - selectedAppName: appName, - workflowId, - doesWorkflowContainsWebhook: selectedCINode?.type === WorkflowNodeType.WEBHOOK, - ciNodeId: selectedCINode?.id, - }) - } - - return null - } - const renderApprovalMaterial = () => { if (ApprovalMaterialModal && location.search.includes(TRIGGER_VIEW_PARAMS.APPROVAL_NODE)) { const { node, appId } = getSelectedNodeAndAppId(filteredWorkflows, location.search) - if (!node?.id || !appId) { - showError('Invalid node id') - return null - } - return ( - {renderCDMaterial()} + {renderBulkCDMaterial()} {renderBulkCIMaterial()} {renderApprovalMaterial()} diff --git a/src/components/ApplicationGroup/Details/TriggerView/types.ts b/src/components/ApplicationGroup/Details/TriggerView/types.ts index 3f2e723d05..cb6f4c913a 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/types.ts +++ b/src/components/ApplicationGroup/Details/TriggerView/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { CommonNodeAttr } from '@devtron-labs/devtron-fe-common-lib' - // Legacy components with no props so setting to any export interface BulkSourceChangeProps { closePopup: any @@ -29,12 +27,3 @@ export interface SourceUpdateResponseModalProps { responseList: any isLoading: any } - -export interface RenderCDMaterialContentProps { - node: CommonNodeAttr - appId: number - selectedAppName: string - workflowId: string - doesWorkflowContainsWebhook: boolean - ciNodeId: string -} diff --git a/src/components/ApplicationGroup/Details/TriggerView/utils.ts b/src/components/ApplicationGroup/Details/TriggerView/utils.ts index e0ae7f0ea5..7303d19060 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/utils.ts +++ b/src/components/ApplicationGroup/Details/TriggerView/utils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CommonNodeAttr, DeploymentNodeType, WorkflowNodeType, WorkflowType } from '@devtron-labs/devtron-fe-common-lib' +import { CommonNodeAttr, DeploymentNodeType, WorkflowType } from '@devtron-labs/devtron-fe-common-lib' import { DeployImageContentProps } from '@Components/app/details/triggerView/DeployImageModal/types' import { getNodeIdAndTypeFromSearch } from '@Components/app/details/triggerView/TriggerView.utils' @@ -56,45 +56,6 @@ export const getSelectedAppListForBulkStrategy = ( return appList.filter(({ pipelineId }) => feasiblePipelineIds.has(pipelineId)) } -export const getSelectedNodeAndMeta = ( - workflows: WorkflowType[], - search: string, -): { node: CommonNodeAttr; workflowId: string; appId: number; appName: string; selectedCINode: CommonNodeAttr } => { - const { cdNodeId, nodeType } = getNodeIdAndTypeFromSearch(search) - - const result = workflows.reduce( - (acc, workflow) => { - if (acc.node) return acc - const foundNode = workflow.nodes.find((node) => cdNodeId === node.id && node.type === nodeType) - - if (foundNode) { - const selectedCINode = workflow.nodes.find( - (node) => node.type === WorkflowNodeType.CI || node.type === WorkflowNodeType.WEBHOOK, - ) - return { - node: foundNode, - workflowId: workflow.id, - appId: workflow.appId, - appName: workflow.name, - selectedCINode, - } - } - return acc - }, - { node: undefined, workflowId: undefined, appId: undefined, appName: undefined, selectedCINode: undefined }, - ) - - return ( - result || { - node: undefined, - workflowId: undefined, - appId: undefined, - appName: undefined, - selectedCINode: undefined, - } - ) -} - export const getSelectedNodeAndAppId = ( workflows: WorkflowType[], search: string, diff --git a/src/components/app/details/triggerView/CDMaterial.tsx b/src/components/app/details/triggerView/CDMaterial.tsx new file mode 100644 index 0000000000..ce595b0bc0 --- /dev/null +++ b/src/components/app/details/triggerView/CDMaterial.tsx @@ -0,0 +1,66 @@ +import { useLocation } from 'react-router-dom' + +import { CommonNodeAttr, DeploymentNodeType } from '@devtron-labs/devtron-fe-common-lib' + +import { getCDPipelineURL } from '@Components/common' + +import { TRIGGER_VIEW_PARAMS } from './Constants' +import { DeployImageModal } from './DeployImageModal' +import { getSelectedNodeFromWorkflows } from './TriggerView.utils' +import { CDMaterialProps, MATERIAL_TYPE } from './types' + +const CDMaterial = ({ workflows, handleClose, handleSuccess }: CDMaterialProps) => { + const location = useLocation() + + if ( + location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) || + location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE) + ) { + const cdNode: CommonNodeAttr = getSelectedNodeFromWorkflows(workflows, location.search) + + const materialType = location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) + ? MATERIAL_TYPE.inputMaterialList + : MATERIAL_TYPE.rollbackMaterialList + + const selectedWorkflow = workflows.find((wf) => wf.nodes.some((node) => node.id === cdNode.id)) + const selectedCINode = selectedWorkflow?.nodes.find((node) => node.type === 'CI' || node.type === 'WEBHOOK') + const doesWorkflowContainsWebhook = selectedCINode?.type === 'WEBHOOK' + + const { appId } = selectedWorkflow + + const configurePluginURL = getCDPipelineURL( + String(appId), + selectedWorkflow?.id || '0', + doesWorkflowContainsWebhook ? '0' : selectedCINode?.id, + doesWorkflowContainsWebhook, + cdNode.id || '0', + true, + ) + + return ( + + ) + } + + return null +} + +export default CDMaterial diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx index d6c34ab288..bfcacedef0 100644 --- a/src/components/app/details/triggerView/TriggerView.tsx +++ b/src/components/app/details/triggerView/TriggerView.tsx @@ -17,31 +17,20 @@ import React, { useState } from 'react' import { Route, Switch, useHistory, useLocation, useParams, useRouteMatch } from 'react-router-dom' -import { - CommonNodeAttr, - DeploymentNodeType, - DocLink, - ErrorScreenManager, - Progressing, -} from '@devtron-labs/devtron-fe-common-lib' +import { DocLink, ErrorScreenManager, Progressing } from '@devtron-labs/devtron-fe-common-lib' import { getExternalCIConfig } from '@Components/ciPipeline/Webhook/webhook.service' import { URLS } from '../../../../config' import { APP_DETAILS } from '../../../../config/constantMessaging' import { LinkedCIDetail } from '../../../../Pages/Shared/LinkedCIDetailsModal' -import { - getCDPipelineURL, - importComponentFromFELibrary, - InValidHostUrlWarningBlock, - useAppContext, -} from '../../../common' +import { importComponentFromFELibrary, InValidHostUrlWarningBlock, useAppContext } from '../../../common' import { getModuleInfo } from '../../../v2/devtronStackManager/DevtronStackManager.service' import { AppNotConfigured } from '../appDetails/AppDetails' import { Workflow } from './workflow/Workflow' import { BuildImageModal } from './BuildImageModal' +import CDMaterial from './CDMaterial' import { TRIGGER_VIEW_PARAMS } from './Constants' -import { DeployImageModal } from './DeployImageModal' import { useTriggerViewServices } from './TriggerView.service' import { getSelectedNodeFromWorkflows, shouldRenderWebhookAddImageModal } from './TriggerView.utils' import { CIMaterialRouterProps, MATERIAL_TYPE, TriggerViewProps } from './types' @@ -110,74 +99,21 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { history.push(match.url) } - const renderCDMaterial = () => { - if ( - location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) || - location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE) - ) { - const cdNode: CommonNodeAttr = getSelectedNodeFromWorkflows(workflows, location.search) - - const materialType = location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) - ? MATERIAL_TYPE.inputMaterialList - : MATERIAL_TYPE.rollbackMaterialList - - const selectedWorkflow = workflows.find((wf) => wf.nodes.some((node) => node.id === cdNode.id)) - const selectedCINode = selectedWorkflow?.nodes.find((node) => node.type === 'CI' || node.type === 'WEBHOOK') - const doesWorkflowContainsWebhook = selectedCINode?.type === 'WEBHOOK' - const configurePluginURL = getCDPipelineURL( - appId, - selectedWorkflow?.id || '0', - doesWorkflowContainsWebhook ? '0' : selectedCINode?.id, - doesWorkflowContainsWebhook, - cdNode.id || '0', - true, - ) - - return ( - - ) - } - - return null - } - const renderApprovalMaterial = () => { if (ApprovalMaterialModal && location.search.includes(TRIGGER_VIEW_PARAMS.APPROVAL_NODE)) { const node = getSelectedNodeFromWorkflows(workflows, location.search) - if (!node.id) { - return null - } - return ( ) @@ -285,7 +221,11 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { - {renderCDMaterial()} + {renderApprovalMaterial()}
{WorkflowActionRouter && ( diff --git a/src/components/app/details/triggerView/TriggerView.utils.tsx b/src/components/app/details/triggerView/TriggerView.utils.tsx index ba533bb7ef..0d9d013019 100644 --- a/src/components/app/details/triggerView/TriggerView.utils.tsx +++ b/src/components/app/details/triggerView/TriggerView.utils.tsx @@ -214,16 +214,14 @@ export const getNodeIdAndTypeFromSearch = (search: string) => { export const getSelectedNodeFromWorkflows = (workflows: WorkflowType[], search: string): CommonNodeAttr => { const { cdNodeId, nodeType } = getNodeIdAndTypeFromSearch(search) - if (!cdNodeId) { - return {} as CommonNodeAttr - } - - // Use flatMap to flatten all nodes, then find the matching node - const allNodes = workflows.flatMap((workflow) => workflow.nodes) - const foundNode = allNodes.find((n) => cdNodeId === n.id && n.type === nodeType) + if (cdNodeId) { + // Use flatMap to flatten all nodes, then find the matching node + const allNodes = workflows.flatMap((workflow) => workflow.nodes) + const foundNode = allNodes.find((n) => cdNodeId === n.id && n.type === nodeType) - if (foundNode) { - return foundNode + if (foundNode) { + return foundNode + } } return {} as CommonNodeAttr diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts index 2f075ec190..0289ba24c8 100644 --- a/src/components/app/details/triggerView/types.ts +++ b/src/components/app/details/triggerView/types.ts @@ -14,43 +14,34 @@ * limitations under the License. */ -import React from 'react' import { RouteComponentProps } from 'react-router-dom' import { AppConfigProps, ArtifactPromotionMetadata, - CDMaterialResponseType, - CDMaterialSidebarType, CDMaterialType, - CDModalTabType, CdPipeline, CIBuildConfigType, CIMaterialType, CiPipeline, CommonNodeAttr, - ConsequenceType, DeploymentAppTypes, DeploymentHistoryDetail, DeploymentNodeType, DeploymentWithConfigType, DynamicDataTableCellValidationState, - FilterConditionsListType, - ImageComment, Material, PipelineType, PolicyKindType, - ReleaseTag, RuntimePluginVariables, TaskErrorObj, - UploadFileDTO, - UploadFileProps, WorkflowType, } from '@devtron-labs/devtron-fe-common-lib' import { CIPipelineBuildType } from '@Components/ciPipeline/types' import { EnvironmentWithSelectPickerType } from '@Components/CIPipelineN/types' +import { DeployImageModalProps } from './DeployImageModal/types' import { Offset, WorkflowDimensions } from './config' export interface RuntimeParamsErrorState { @@ -60,112 +51,6 @@ export interface RuntimeParamsErrorState { export type HandleRuntimeParamChange = (updatedRuntimeParams: RuntimePluginVariables[]) => void -export type HandleRuntimeParamErrorState = (updatedErrorState: RuntimeParamsErrorState) => void - -type CDMaterialBulkRuntimeParams = - | { - isFromBulkCD: true - bulkRuntimeParams: RuntimePluginVariables[] - handleBulkRuntimeParamChange: HandleRuntimeParamChange - bulkRuntimeParamErrorState: RuntimeParamsErrorState - handleBulkRuntimeParamError: HandleRuntimeParamErrorState - bulkSidebarTab: CDMaterialSidebarType - bulkUploadFile: (props: UploadFileProps) => Promise - } - | { - isFromBulkCD?: false - bulkRuntimeParams?: never - handleBulkRuntimeParamChange?: never - bulkRuntimeParamErrorState?: never - handleBulkRuntimeParamError?: never - bulkSidebarTab?: never - bulkUploadFile?: never - } - -type CDMaterialPluginWarningProps = - | { - showPluginWarningBeforeTrigger: boolean - consequence: ConsequenceType - configurePluginURL: string - } - | { - showPluginWarningBeforeTrigger?: never - consequence?: never - configurePluginURL?: never - } - -export type CDMaterialProps = { - material?: CDMaterialType[] - isLoading: boolean - materialType: string - envName: string - envId?: number - redirectToCD?: () => void - stageType: DeploymentNodeType - changeTab?: ( - materrialId: string | number, - artifactId: number, - tab: CDModalTabType, - selectedCDDetail?: { id: number; type: DeploymentNodeType }, - appId?: number, - ) => void - selectImage?: ( - index: number, - materialType: string, - selectedCDDetail?: { id: number; type: DeploymentNodeType }, - appId?: number, - ) => void - toggleSourceInfo?: (materialIndex: number, selectedCDDetail?: { id: number; type: DeploymentNodeType }) => void - closeCDModal: (e: React.MouseEvent) => void - onClickRollbackMaterial?: ( - cdNodeId: number, - offset?: number, - size?: number, - callback?: (loadingMore: boolean, noMoreImages?: boolean) => void, - searchText?: string, - ) => void - parentPipelineId?: string - parentPipelineType?: string - parentEnvironmentName?: string - hideInfoTabsContainer?: boolean - appId?: number - pipelineId?: number - isFromBulkCD?: boolean - requestedUserId?: number - triggerType?: string - isVirtualEnvironment?: boolean - isSaveLoading?: boolean - ciPipelineId?: number - appReleaseTagNames?: string[] - setAppReleaseTagNames?: (appReleaseTags: string[]) => void - tagsEditable?: boolean - hideImageTaggingHardDelete?: boolean - setTagsEditable?: (tagsEditable: boolean) => void - updateCurrentAppMaterial?: (matId: number, releaseTags?: ReleaseTag[], imageComment?: ImageComment) => void - handleMaterialFilters?: ( - text: string, - cdNodeId, - nodeType: DeploymentNodeType, - isApprovalNode?: boolean, - fromRollback?: boolean, - ) => void - searchImageTag?: string - resourceFilters?: FilterConditionsListType[] - updateBulkCDMaterialsItem?: (singleCDMaterialResponse: CDMaterialResponseType) => void - deploymentAppType?: DeploymentAppTypes - selectedImageFromBulk?: string - isSuperAdmin?: boolean - isRedirectedFromAppDetails?: boolean - /** - * App name coming from app group view - * To be consumed through variable called appName - */ - selectedAppName?: string - isTriggerBlockedDueToPlugin?: boolean - handleSuccess?: () => void -} & CDMaterialBulkRuntimeParams & - CDMaterialPluginWarningProps - export interface ConfigToDeployOptionType { label: string value: DeploymentWithConfigType @@ -624,11 +509,6 @@ export interface AddDimensionsToDownstreamDeploymentsParams { startY: number } -export interface RenderCTAType { - mat: CDMaterialType - disableSelection: boolean -} - export interface WebhookPayload { eventTime: string matchedFiltersCount: number @@ -700,3 +580,7 @@ export interface GetCDNodeSearchParams { nodeType?: DeploymentNodeType fromAppGroup?: boolean } + +export interface CDMaterialProps extends Pick { + workflows: WorkflowType[] +} diff --git a/src/components/app/types.ts b/src/components/app/types.ts index 84f2c699c8..f44227b81d 100644 --- a/src/components/app/types.ts +++ b/src/components/app/types.ts @@ -35,7 +35,6 @@ import { CreateAppFormStateType } from '@Pages/App/CreateAppModal/types' import { GroupFilterType } from '../ApplicationGroup/AppGroup.types' import { DetailsType, ErrorItem, HibernationModalTypes } from './details/appDetails/appDetails.type' import { DeployImageModalProps } from './details/triggerView/DeployImageModal/types' -import { CDMaterialProps } from './details/triggerView/types' interface CDModalProps { cdPipelineId?: number @@ -541,7 +540,7 @@ export interface AppDetailsCDModalType Pick { cdModal: CDModalProps appName?: string - handleSuccess?: CDMaterialProps['handleSuccess'] + handleSuccess?: DeployImageModalProps['handleSuccess'] materialType: DeployImageModalProps['materialType'] closeCDModal: () => void } From 25529efe0bc36ca8c5d97c667470fa827727a0e5 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Fri, 8 Aug 2025 15:33:18 +0530 Subject: [PATCH 3/9] chore: sort imports --- .../ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx index b1bacb0753..12f806a9fd 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx +++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx @@ -42,6 +42,7 @@ import { } from '@devtron-labs/devtron-fe-common-lib' import { BuildImageModal, BulkBuildImageModal } from '@Components/app/details/triggerView/BuildImageModal' +import CDMaterial from '@Components/app/details/triggerView/CDMaterial' import { BulkDeployModal } from '@Components/app/details/triggerView/DeployImageModal' import { shouldRenderWebhookAddImageModal } from '@Components/app/details/triggerView/TriggerView.utils' import { getExternalCIConfig } from '@Components/ciPipeline/Webhook/webhook.service' @@ -77,7 +78,6 @@ import BulkSourceChange from './BulkSourceChange' import { getSelectedNodeAndAppId } from './utils' import './EnvTriggerView.scss' -import CDMaterial from '@Components/app/details/triggerView/CDMaterial' const ApprovalMaterialModal = importComponentFromFELibrary('ApprovalMaterialModal') const processDeploymentWindowStateAppGroup = importComponentFromFELibrary( From 3e8be4b93e546bc5d44b4a6d554649f402e8c467 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Fri, 8 Aug 2025 15:47:40 +0530 Subject: [PATCH 4/9] chore: use pipeline id from url --- .../app/details/triggerView/CDMaterial.tsx | 14 +++++++------- .../app/details/triggerView/TriggerView.tsx | 4 ++-- .../app/details/triggerView/TriggerView.utils.tsx | 9 ++++++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/app/details/triggerView/CDMaterial.tsx b/src/components/app/details/triggerView/CDMaterial.tsx index ce595b0bc0..91da83a55b 100644 --- a/src/components/app/details/triggerView/CDMaterial.tsx +++ b/src/components/app/details/triggerView/CDMaterial.tsx @@ -1,6 +1,6 @@ import { useLocation } from 'react-router-dom' -import { CommonNodeAttr, DeploymentNodeType } from '@devtron-labs/devtron-fe-common-lib' +import { DeploymentNodeType } from '@devtron-labs/devtron-fe-common-lib' import { getCDPipelineURL } from '@Components/common' @@ -16,17 +16,17 @@ const CDMaterial = ({ workflows, handleClose, handleSuccess }: CDMaterialProps) location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) || location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE) ) { - const cdNode: CommonNodeAttr = getSelectedNodeFromWorkflows(workflows, location.search) + const { node: cdNode, cdNodeId } = getSelectedNodeFromWorkflows(workflows, location.search) const materialType = location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) ? MATERIAL_TYPE.inputMaterialList : MATERIAL_TYPE.rollbackMaterialList - const selectedWorkflow = workflows.find((wf) => wf.nodes.some((node) => node.id === cdNode.id)) + const selectedWorkflow = workflows.find((wf) => wf.nodes.some((node) => node.id === cdNodeId)) const selectedCINode = selectedWorkflow?.nodes.find((node) => node.type === 'CI' || node.type === 'WEBHOOK') const doesWorkflowContainsWebhook = selectedCINode?.type === 'WEBHOOK' - const { appId } = selectedWorkflow + const appId = selectedWorkflow?.appId ?? 0 const configurePluginURL = getCDPipelineURL( String(appId), @@ -40,12 +40,12 @@ const CDMaterial = ({ workflows, handleClose, handleSuccess }: CDMaterialProps) return ( { const renderApprovalMaterial = () => { if (ApprovalMaterialModal && location.search.includes(TRIGGER_VIEW_PARAMS.APPROVAL_NODE)) { - const node = getSelectedNodeFromWorkflows(workflows, location.search) + const { node, cdNodeId } = getSelectedNodeFromWorkflows(workflows, location.search) return ( { stageType={node?.type} closeApprovalModal={closeApprovalModal} appId={+appId} - pipelineId={node?.id} + pipelineId={cdNodeId} getModuleInfo={getModuleInfo} ciPipelineId={node?.connectingCiPipelineId} history={history} diff --git a/src/components/app/details/triggerView/TriggerView.utils.tsx b/src/components/app/details/triggerView/TriggerView.utils.tsx index 0d9d013019..32c9957153 100644 --- a/src/components/app/details/triggerView/TriggerView.utils.tsx +++ b/src/components/app/details/triggerView/TriggerView.utils.tsx @@ -211,7 +211,10 @@ export const getNodeIdAndTypeFromSearch = (search: string) => { return { cdNodeId, nodeType } } -export const getSelectedNodeFromWorkflows = (workflows: WorkflowType[], search: string): CommonNodeAttr => { +export const getSelectedNodeFromWorkflows = ( + workflows: WorkflowType[], + search: string, +): { cdNodeId: string; node: CommonNodeAttr } => { const { cdNodeId, nodeType } = getNodeIdAndTypeFromSearch(search) if (cdNodeId) { @@ -220,11 +223,11 @@ export const getSelectedNodeFromWorkflows = (workflows: WorkflowType[], search: const foundNode = allNodes.find((n) => cdNodeId === n.id && n.type === nodeType) if (foundNode) { - return foundNode + return { cdNodeId, node: foundNode } } } - return {} as CommonNodeAttr + return { cdNodeId: cdNodeId ?? '0', node: {} as CommonNodeAttr } } export const getCDNodeActionSearch = ({ From 57cd39c0528f020000dacc90cd1112caf57cfe6d Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Mon, 11 Aug 2025 00:46:02 +0530 Subject: [PATCH 5/9] refactor: update error handling and improve type definitions in deployment components --- .../ApplicationGroup/AppGroup.types.ts | 3 +- .../Details/TriggerView/EnvTriggerView.tsx | 3 +- .../app/details/appDetails/AppDetails.tsx | 1 - .../details/appDetails/AppDetailsCDModal.tsx | 2 - .../DeployImageModal/BulkDeployEmptyState.tsx | 2 +- .../DeployImageModal/BulkDeployModal.tsx | 127 +++++++++++------- .../DeployImageModal/BulkTriggerSidebar.tsx | 21 ++- .../DeployImageModal/DeployImageContent.tsx | 25 +++- .../DeployImageModal/DeployImageModal.tsx | 5 +- .../triggerView/DeployImageModal/types.ts | 2 + .../triggerView/DeployImageModal/utils.tsx | 23 ++-- .../app/details/triggerView/TriggerView.tsx | 1 - src/components/app/types.ts | 7 +- 13 files changed, 146 insertions(+), 76 deletions(-) diff --git a/src/components/ApplicationGroup/AppGroup.types.ts b/src/components/ApplicationGroup/AppGroup.types.ts index e12bfc56c2..7788a53a51 100644 --- a/src/components/ApplicationGroup/AppGroup.types.ts +++ b/src/components/ApplicationGroup/AppGroup.types.ts @@ -79,7 +79,7 @@ export type BulkCDDetailDerivedFromNode = Required< > > & { stageNotAvailable: boolean - warningMessage: string + errorMessage: string triggerBlockedInfo: TriggerBlockedInfo consequence: CommonNodeAttr['pluginBlockState'] showPluginWarning: CommonNodeAttr['showPluginWarning'] @@ -92,6 +92,7 @@ export type BulkCDDetailType = BulkCDDetailDerivedFromNode & */ areMaterialsLoading: boolean materialError: ServerErrors | null + tagsWarningMessage: string } export interface BulkCDDetailTypeResponse { diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx index bcd169b71b..f9a26d648b 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx +++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx @@ -492,7 +492,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT workflows={filteredWorkflows} isVirtualEnvironment={isVirtualEnv} envId={+envId} - handleSuccess={reloadTriggerView} + handleSuccess={getWorkflowStatusData} /> ) } @@ -611,7 +611,6 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT return ( = ({ environmentName={appDetails.environmentName} isVirtualEnvironment={appDetails.isVirtualEnvironment} deploymentAppType={appDetails.deploymentAppType} - loadingDetails={loadingDetails} cdModal={{ cdPipelineId: appDetails.cdPipelineId, ciPipelineId: appDetails.ciPipelineId, diff --git a/src/components/app/details/appDetails/AppDetailsCDModal.tsx b/src/components/app/details/appDetails/AppDetailsCDModal.tsx index 1f339a0960..a1783e1206 100644 --- a/src/components/app/details/appDetails/AppDetailsCDModal.tsx +++ b/src/components/app/details/appDetails/AppDetailsCDModal.tsx @@ -34,7 +34,6 @@ const AppDetailsCDModal = ({ cdModal, deploymentAppType, isVirtualEnvironment, - loadingDetails, environmentName, handleSuccess, materialType, @@ -55,7 +54,6 @@ const AppDetailsCDModal = ({ ApprovalMaterialModal && location.search.includes(TRIGGER_VIEW_PARAMS.APPROVAL_NODE) && ( ) diff --git a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx index 4c798ac8f9..e7ca2892df 100644 --- a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx @@ -1,4 +1,5 @@ import { Dispatch, SetStateAction, SyntheticEvent, useEffect, useMemo, useRef, useState } from 'react' +import { Prompt } from 'react-router-dom' import { AnimatedDeployButton, @@ -7,6 +8,7 @@ import { ButtonStyleType, CDMaterialResponseType, CDMaterialServiceEnum, + DEFAULT_ROUTE_PROMPT_MESSAGE, DeploymentNodeType, DeploymentStrategyTypeWithDefault, Drawer, @@ -27,6 +29,7 @@ import { uploadCDPipelineFile, useAsync, useMainContext, + usePrompt, } from '@devtron-labs/devtron-fe-common-lib' import { ResponseRowType } from '@Components/ApplicationGroup/AppGroup.types' @@ -97,6 +100,8 @@ const BulkDeployModal = ({ const isSecurityModuleInstalled = moduleInfoRes && moduleInfoRes?.result?.status === ModuleStatus.INSTALLED const isCDStage = stageType === DeploymentNodeType.CD + usePrompt({ shouldPrompt: isDeploymentLoading }) + useEffect( () => () => { initialDataAbortControllerRef.current.abort() @@ -139,7 +144,7 @@ const BulkDeployModal = ({ (node) => node.type === stageType && +node.environmentId === +envId, ) - if (!currentStageNode) { + if (!currentStageNode || baseBulkCDDetailMap[workflow.appId].errorMessage) { return () => null } @@ -167,7 +172,7 @@ const BulkDeployModal = ({ ) const appEnvList = validWorkflows - .filter((workflow) => !baseBulkCDDetailMap[workflow.appId].warningMessage) + .filter((workflow) => !baseBulkCDDetailMap[workflow.appId].errorMessage) .map((workflow) => ({ appId: workflow.appId, envId: +envId, @@ -223,7 +228,8 @@ const BulkDeployModal = ({ }, })) - const { deploymentWindowMetadata, materialError, warningMessage } = response[selectedAppId] || {} + const { deploymentWindowMetadata, materialError, errorMessage, tagsWarningMessage } = + response[selectedAppId] || {} if (materialError) { showError(materialError) @@ -243,7 +249,8 @@ const BulkDeployModal = ({ [selectedAppId]: { ...prev[selectedAppId], materialResponse: response[selectedAppId]?.materialResponse, - warningMessage, + errorMessage, + tagsWarningMessage, materialError, deploymentWindowMetadata, deployViewState: { @@ -385,8 +392,6 @@ const BulkDeployModal = ({ return } - setIsDeploymentLoading(true) - const { cdTriggerPromiseFunctions, triggeredAppIds } = getTriggerCDPromiseMethods({ appInfoMap, appsToRetry, @@ -396,11 +401,14 @@ const BulkDeployModal = ({ bulkDeploymentStrategy, }) + setIsDeploymentLoading(true) + setNumberOfAppsLoading(triggeredAppIds.length) + if (!triggeredAppIds.length) { setIsDeploymentLoading(false) ToastManager.showToast({ variant: ToastVariantType.error, - description: 'No applications selected for deployment', + description: 'No valid applications are present for deployment', }) return } @@ -418,6 +426,7 @@ const BulkDeployModal = ({ setResponseList(newResponseList) setIsDeploymentLoading(false) + setNumberOfAppsLoading(0) } const setDeployViewState: DeployImageContentProps['setDeployViewState'] = (getUpdatedDeployViewState) => { @@ -471,19 +480,13 @@ const BulkDeployModal = ({ appDetails.materialResponse?.materials || [], ) - const { tagsWarning: previousTagWarning } = getUpdatedMaterialsForTagSelection( - selectedImageTagOption.value, - appDetails.materialResponse?.materials || [], - ) - updatedAppInfoMap[appDetails.appId] = { ...appDetails, materialResponse: { ...appDetails.materialResponse, materials: updatedMaterials, }, - warningMessage: - previousTagWarning || !appDetails.warningMessage ? tagsWarning : appDetails.warningMessage, + tagsWarningMessage: tagsWarning, } }) return updatedAppInfoMap @@ -502,16 +505,37 @@ const BulkDeployModal = ({ await onClickDeploy(e) } - const isDeployButtonDisabled = useMemo( - () => + const onImageSelection: DeployImageContentProps['onImageSelection'] = () => { + // Will just clear the tagsWarningMessage for app others are handled in DeployImageContent + setAppInfoMap((prev) => ({ + ...prev, + [selectedAppId]: { + ...prev[selectedAppId], + tagsWarningMessage: '', + }, + })) + } + + const isDeployButtonDisabled = useMemo(() => { + const atleastOneImageSelected = Object.values(appInfoMap).some((appDetails) => + (appDetails.materialResponse?.materials || []).some((material) => material.isSelected), + ) + + if (!atleastOneImageSelected) { + return true + } + + return ( isDeploymentLoading || isLoadingAppInfoMap || + // Not disabling deploy button even if there is a warning message, since apps with warning will not have selected materials + // and hence will not be deployed Object.values(appInfoMap).some((appDetails) => { - const { materialResponse, deployViewState, areMaterialsLoading } = appDetails - return areMaterialsLoading || !materialResponse || !deployViewState - }), - [appInfoMap, isDeploymentLoading, isLoadingAppInfoMap], - ) + const { areMaterialsLoading } = appDetails + return areMaterialsLoading + }) + ) + }, [appInfoMap, isDeploymentLoading, isLoadingAppInfoMap]) const canDeployWithoutApproval = useMemo( () => @@ -632,6 +656,7 @@ const BulkDeployModal = ({ handleTagChange={handleTagChange} changeApp={changeApp} selectedTagName={selectedImageTagOption.value} + onImageSelection={onImageSelection} /> ) } @@ -682,7 +707,7 @@ const BulkDeployModal = ({ onButtonClick={onClickStartDeploy} disabled={isDeployButtonDisabled} isLoading={isDeploymentLoading} - animateStartIcon={isCDStage} + animateStartIcon={isCDStage && !isDeployButtonDisabled} style={ canDeployWithoutApproval || canImageApproverDeploy ? ButtonStyleType.warning @@ -701,36 +726,42 @@ const BulkDeployModal = ({ } return ( - -
-
- + <> + +
+
+ + +
{renderContent()}
+
-
{renderContent()}
+ {isLoadingAppInfoMap || showStrategyFeasibilityPage ? null : renderFooter()}
- {isLoadingAppInfoMap || showStrategyFeasibilityPage ? null : renderFooter()} -
+ {showResistanceBox && ( + + )} + - {showResistanceBox && ( - - )} - + + ) } diff --git a/src/components/app/details/triggerView/DeployImageModal/BulkTriggerSidebar.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkTriggerSidebar.tsx index ff682ddc59..6c128a6447 100644 --- a/src/components/app/details/triggerView/DeployImageModal/BulkTriggerSidebar.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/BulkTriggerSidebar.tsx @@ -152,12 +152,25 @@ const BulkTriggerSidebar = ({ return } - if ((!!app.warningMessage && !app.showPluginWarning) || app.materialError?.errors?.length) { + const isRuntimeParamsErrorPresent = + app.deployViewState?.runtimeParamsErrorState && !app.deployViewState.runtimeParamsErrorState.isValid + + if ( + (!!app.errorMessage && !app.showPluginWarning) || + app.materialError?.errors?.length || + app.tagsWarningMessage || + isRuntimeParamsErrorPresent + ) { return (
- - - {app.warningMessage || app.materialError?.errors?.[0]?.userMessage} +
+ +
+ + {app.errorMessage || + app.materialError?.errors?.[0]?.userMessage || + app.tagsWarningMessage || + 'Invalid runtime parameters'}
) diff --git a/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx b/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx index f602c9deac..52badd211a 100644 --- a/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/DeployImageContent.tsx @@ -21,6 +21,7 @@ import { InfoBlock, isNullOrUndefined, MaterialInfo, + noop, Progressing, SearchBar, SegmentedControlProps, @@ -59,6 +60,7 @@ const RuntimeParameters = importComponentFromFELibrary('RuntimeParameters', null const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function') const CDMaterialInfo = importComponentFromFELibrary('CDMaterialInfo') const ConfiguredFilters = importComponentFromFELibrary('ConfiguredFilters') +const DeploymentWindowInfoBar = importComponentFromFELibrary('DeploymentWindowInfoBar') const DeployImageContent = ({ appId, @@ -91,6 +93,7 @@ const DeployImageContent = ({ selectedTagName, handleTagChange, changeApp, + onImageSelection = noop, }: DeployImageContentProps) => { // WARNING: Pls try not to create a useState in this component, it is supposed to be a dumb component. const history = useHistory() @@ -110,6 +113,11 @@ const DeployImageContent = ({ const isConsumedImageAvailable = getIsConsumedImageAvailable(materials) const isPreOrPostCD = stageType === DeploymentNodeType.PRECD || stageType === DeploymentNodeType.POSTCD const isCDNode = stageType === DeploymentNodeType.CD + const showDeploymentWindowInfoBar = !!( + DeploymentWindowInfoBar && + isBulkTrigger && + deploymentWindowMetadata.warningMessage + ) const { searchText, @@ -209,6 +217,7 @@ const DeployImageContent = ({ } const handleImageSelection: ImageSelectionCTAProps['handleImageSelection'] = (materialIndex) => { + onImageSelection(materialIndex) setMaterialResponse((prevData) => { const updatedMaterialResponse = structuredClone(prevData) return { @@ -668,6 +677,7 @@ const DeployImageContent = ({ return ( <> {!showFiltersView && + !isBulkTrigger && isApprovalConfigured && !isExceptionUser && ApprovedImagesMessage && @@ -694,9 +704,20 @@ const DeployImageContent = ({ > {renderSidebar()}
- {renderContent()} + {showDeploymentWindowInfoBar && ( + + )} +
+ {renderContent()} +
diff --git a/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx b/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx index d2c18850fc..1894e39c50 100644 --- a/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/DeployImageModal.tsx @@ -175,6 +175,7 @@ const DeployImageModal = ({ const allowWarningWithTippyNodeTypeProp = getAllowWarningWithTippyNodeTypeProp(stageType) const runtimeParamsList = materialResponse?.runtimeParams || [] const requestedUserId = materialResponse?.requestedUserId + const showFiltersView = deployViewState.showAppliedFilters || deployViewState.showConfiguredFilters usePrompt({ shouldPrompt: isDeploymentLoading }) @@ -715,7 +716,7 @@ const DeployImageModal = ({ onClick={stopPropagation} >
- {!deployViewState.showAppliedFilters && !deployViewState.showConfiguredFilters && ( + {!showFiltersView && ( {renderContent()}
- {initialDataError || isInitialDataLoading || materialList.length === 0 ? null : ( + {initialDataError || isInitialDataLoading || showFiltersView || materialList.length === 0 ? null : (
{renderFooter()}
diff --git a/src/components/app/details/triggerView/DeployImageModal/types.ts b/src/components/app/details/triggerView/DeployImageModal/types.ts index db4d51ba62..2353fc52cd 100644 --- a/src/components/app/details/triggerView/DeployImageModal/types.ts +++ b/src/components/app/details/triggerView/DeployImageModal/types.ts @@ -162,6 +162,7 @@ export type DeployImageContentProps = Pick< appInfoMap: Record selectedTagName: string handleTagChange: (tagOption: SelectPickerOptionType) => void + onImageSelection: (materialIndex: number) => void changeApp: (appId: number) => void policyConsequences?: never } @@ -172,6 +173,7 @@ export type DeployImageContentProps = Pick< handleTagChange?: never policyConsequences: PolicyConsequencesDTO changeApp?: never + onImageSelection?: never } ) diff --git a/src/components/app/details/triggerView/DeployImageModal/utils.tsx b/src/components/app/details/triggerView/DeployImageModal/utils.tsx index bb68c1b868..d1e8f566a2 100644 --- a/src/components/app/details/triggerView/DeployImageModal/utils.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/utils.tsx @@ -495,7 +495,7 @@ export const getBaseBulkCDDetailsMap = (validWorkflows: WorkflowType[], stageTyp true, ), triggerType: currentStageNode?.triggerType, - warningMessage: noStageWarning || blockedStageWarning || '', + errorMessage: noStageWarning || blockedStageWarning || '', triggerBlockedInfo: currentStageNode?.triggerBlockedInfo, stageNotAvailable: !currentStageNode, showPluginWarning: currentStageNode?.showPluginWarning, @@ -525,29 +525,36 @@ export const getBulkCDDetailsMapFromResponse: GetBulkCDDetailsMapFromResponseTyp ) const parsedTagsWarning = searchText ? '' : tagsWarning + const newMaterials = searchText ? materialResponse.value.materials : updatedMaterials + + // In case of no search, we will show tag not found + const noImageSelectedWarning = + searchText && !newMaterials.some((material) => material.isSelected) ? 'No image selected' : '' const updatedWarningMessage = - baseBulkCDDetailMap[appId].warningMessage || - deploymentWindowMap[appId]?.warningMessage || - parsedTagsWarning + baseBulkCDDetailMap[appId].errorMessage || + noImageSelectedWarning || + deploymentWindowMap[appId]?.warningMessage // In case of search and reload even though method gives whole state, will only update deploymentWindowMetadata, warningMessage and materialResponse bulkCDDetailsMap[appId] = { ...baseBulkCDDetailMap[appId], materialResponse: { ...materialResponse.value, - materials: searchText ? materialResponse.value.materials : updatedMaterials, + materials: newMaterials, }, - deploymentWindowMetadata: deploymentWindowMap[appId], + deploymentWindowMetadata: deploymentWindowMap[appId] || ({} as DeploymentWindowProfileMetaData), areMaterialsLoading: false, deployViewState: structuredClone(INITIAL_DEPLOY_VIEW_STATE), - warningMessage: updatedWarningMessage, + errorMessage: updatedWarningMessage, + tagsWarningMessage: parsedTagsWarning, materialError: null, } } else { bulkCDDetailsMap[appId] = { ...baseBulkCDDetailMap[appId], - materialResponse: {} as CDMaterialResponseType, + tagsWarningMessage: '', + materialResponse: null, deploymentWindowMetadata: {} as DeploymentWindowProfileMetaData, deployViewState: structuredClone(INITIAL_DEPLOY_VIEW_STATE), materialError: diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx index d6c34ab288..3ecd5ea624 100644 --- a/src/components/app/details/triggerView/TriggerView.tsx +++ b/src/components/app/details/triggerView/TriggerView.tsx @@ -169,7 +169,6 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { return ( , Partial< export interface AppDetailsCDModalType extends Pick< - AppDetails, - 'appId' | 'environmentId' | 'isVirtualEnvironment' | 'deploymentAppType' | 'environmentName' - >, - Pick { + AppDetails, + 'appId' | 'environmentId' | 'isVirtualEnvironment' | 'deploymentAppType' | 'environmentName' + > { cdModal: CDModalProps appName?: string handleSuccess?: CDMaterialProps['handleSuccess'] From 9b4be2906fcade88d26238b0914d9d0d88a03e4b Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Mon, 11 Aug 2025 01:15:36 +0530 Subject: [PATCH 6/9] refactor: enhance approval policy checks and update tag warning logic in deployment utilities --- .../DeployImageModal/BulkDeployModal.tsx | 4 +++ .../triggerView/DeployImageModal/utils.tsx | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx index e7ca2892df..e08388074a 100644 --- a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx @@ -14,6 +14,7 @@ import { Drawer, genericCDMaterialsService, GenericEmptyState, + getIsApprovalPolicyConfigured, Icon, MODAL_TYPE, ModuleNameMap, @@ -478,6 +479,9 @@ const BulkDeployModal = ({ const { tagsWarning, updatedMaterials } = getUpdatedMaterialsForTagSelection( tagOption.value, appDetails.materialResponse?.materials || [], + !getIsApprovalPolicyConfigured( + appDetails.materialResponse?.deploymentApprovalInfo?.approvalConfigData, + ) || getIsExceptionUser(appDetails.materialResponse), ) updatedAppInfoMap[appDetails.appId] = { diff --git a/src/components/app/details/triggerView/DeployImageModal/utils.tsx b/src/components/app/details/triggerView/DeployImageModal/utils.tsx index d1e8f566a2..1ff64d5836 100644 --- a/src/components/app/details/triggerView/DeployImageModal/utils.tsx +++ b/src/components/app/details/triggerView/DeployImageModal/utils.tsx @@ -14,6 +14,7 @@ import { DeploymentWithConfigType, ExcludedImageNode, FilterStates, + getIsApprovalPolicyConfigured, getIsRequestAborted, getStageTitle, Icon, @@ -381,7 +382,11 @@ export const getDeployButtonStyle = ( export const getIsConsumedImageAvailable = (materials: CDMaterialType[]) => materials.some((materialItem) => materialItem.deployed && materialItem.latest) ?? false -const getTagWarningRelatedToMaterial = (updatedMaterials: CDMaterialType[], selectedImageTagName: string): string => { +const getTagWarningRelatedToMaterial = ( + updatedMaterials: CDMaterialType[], + selectedImageTagName: string, + canSelectNonApprovedImage: boolean, +): string => { const selectedImage = updatedMaterials.find((material) => material.isSelected) const selectedTagParsedName = @@ -399,11 +404,23 @@ const getTagWarningRelatedToMaterial = (updatedMaterials: CDMaterialType[], sele return `Tag ${selectedTagParsedName} is not eligible for deployment` } + const isNonApprovedImage = + !selectedImage.userApprovalMetadata || + selectedImage.userApprovalMetadata.approvalRuntimeState !== ApprovalRuntimeStateType.approved + + if (isNonApprovedImage && !canSelectNonApprovedImage) { + return `Tag ${selectedTagParsedName} is not approved` + } + return '' } // If tag is not present, and image is selected we will show mixed tag -export const getUpdatedMaterialsForTagSelection = (tagName: string, materials: CDMaterialType[]) => { +export const getUpdatedMaterialsForTagSelection = ( + tagName: string, + materials: CDMaterialType[], + canSelectNonApprovedImage: boolean, +) => { const sourceMaterials = structuredClone(materials) const updatedMaterials = sourceMaterials.map((material, materialIndex) => { @@ -438,7 +455,7 @@ export const getUpdatedMaterialsForTagSelection = (tagName: string, materials: C const selectedImage = updatedMaterials.find((material) => material.isSelected) - const tagsWarning = getTagWarningRelatedToMaterial(updatedMaterials, tagName) + const tagsWarning = getTagWarningRelatedToMaterial(updatedMaterials, tagName, canSelectNonApprovedImage) if (selectedImage && tagsWarning) { selectedImage.isSelected = false @@ -522,6 +539,8 @@ export const getBulkCDDetailsMapFromResponse: GetBulkCDDetailsMapFromResponseTyp const { tagsWarning, updatedMaterials } = getUpdatedMaterialsForTagSelection( selectedTagName, materialResponse.value.materials, + !getIsApprovalPolicyConfigured(materialResponse?.value.deploymentApprovalInfo?.approvalConfigData) || + getIsExceptionUser(materialResponse.value), ) const parsedTagsWarning = searchText ? '' : tagsWarning From 440cedbc63e11e624097fa77379a1154e82df8d1 Mon Sep 17 00:00:00 2001 From: Arun Jain Date: Mon, 11 Aug 2025 10:56:52 +0530 Subject: [PATCH 7/9] chore: version bump --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 88d5c4ff8c..4279e2a0f6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "homepage": "/dashboard", "dependencies": { - "@devtron-labs/devtron-fe-common-lib": "1.19.0-pre-1", + "@devtron-labs/devtron-fe-common-lib": "1.19.0-beta-2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@rjsf/core": "^5.13.3", "@rjsf/utils": "^5.13.3", diff --git a/yarn.lock b/yarn.lock index 24f6aa426e..eeb73fac6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1722,9 +1722,9 @@ __metadata: languageName: node linkType: hard -"@devtron-labs/devtron-fe-common-lib@npm:1.19.0-pre-1": - version: 1.19.0-pre-1 - resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.19.0-pre-1" +"@devtron-labs/devtron-fe-common-lib@npm:1.19.0-beta-2": + version: 1.19.0-beta-2 + resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.19.0-beta-2" dependencies: "@codemirror/autocomplete": "npm:6.18.6" "@codemirror/lang-json": "npm:6.0.1" @@ -1774,7 +1774,7 @@ __metadata: react-select: 5.8.0 rxjs: ^7.8.1 yaml: ^2.4.1 - checksum: 10c0/70df7bc3e8d33cb4e44a9901b0d747177139cf3b2be57bd76e168c09e4eec56b4fd8069be2d951b8aad7ec5bfb441989d2df12c445e67b298d7bc53a9bd1fe2e + checksum: 10c0/98ca8ce250bc4be7b99a9bda02ec0aa4021d872976d3ad42fc61d82da083fcf506471e8f1f131fc033e028896853cd9844ff948ebec6cd4647e256513628f2ad languageName: node linkType: hard @@ -5721,7 +5721,7 @@ __metadata: version: 0.0.0-use.local resolution: "dashboard@workspace:." dependencies: - "@devtron-labs/devtron-fe-common-lib": "npm:1.19.0-pre-1" + "@devtron-labs/devtron-fe-common-lib": "npm:1.19.0-beta-2" "@esbuild-plugins/node-globals-polyfill": "npm:0.2.3" "@playwright/test": "npm:^1.32.1" "@rjsf/core": "npm:^5.13.3" From e2e8e9561cfe7bbb01ab62378a942a8f3ab3b9c1 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 12 Aug 2025 11:49:13 +0530 Subject: [PATCH 8/9] Add isTriggerView prop to CDMaterial component and update usage in EnvTriggerView --- .../Details/TriggerView/EnvTriggerView.tsx | 1 + src/components/app/details/triggerView/CDMaterial.tsx | 7 ++++--- src/components/app/details/triggerView/TriggerView.tsx | 1 + src/components/app/details/triggerView/types.ts | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx index 9569f7e2c6..83be81921b 100644 --- a/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx +++ b/src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx @@ -756,6 +756,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT workflows={filteredWorkflows} handleClose={revertToPreviousURL} handleSuccess={getWorkflowStatusData} + isTriggerView={false} /> {renderBulkCDMaterial()} {renderBulkCIMaterial()} diff --git a/src/components/app/details/triggerView/CDMaterial.tsx b/src/components/app/details/triggerView/CDMaterial.tsx index 91da83a55b..2d3cca8894 100644 --- a/src/components/app/details/triggerView/CDMaterial.tsx +++ b/src/components/app/details/triggerView/CDMaterial.tsx @@ -2,15 +2,16 @@ import { useLocation } from 'react-router-dom' import { DeploymentNodeType } from '@devtron-labs/devtron-fe-common-lib' -import { getCDPipelineURL } from '@Components/common' +import { getCDPipelineURL, useAppContext } from '@Components/common' import { TRIGGER_VIEW_PARAMS } from './Constants' import { DeployImageModal } from './DeployImageModal' import { getSelectedNodeFromWorkflows } from './TriggerView.utils' import { CDMaterialProps, MATERIAL_TYPE } from './types' -const CDMaterial = ({ workflows, handleClose, handleSuccess }: CDMaterialProps) => { +const CDMaterial = ({ workflows, handleClose, handleSuccess, isTriggerView }: CDMaterialProps) => { const location = useLocation() + const { currentAppName: triggerViewAppName } = useAppContext() if ( location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) || @@ -42,7 +43,7 @@ const CDMaterial = ({ workflows, handleClose, handleSuccess }: CDMaterialProps) materialType={materialType} appId={appId} envId={cdNode.environmentId} - appName={selectedWorkflow?.name ?? ''} + appName={isTriggerView ? triggerViewAppName : selectedWorkflow?.name ?? ''} stageType={cdNode.type as DeploymentNodeType} envName={cdNode.environmentName} pipelineId={Number(cdNodeId)} diff --git a/src/components/app/details/triggerView/TriggerView.tsx b/src/components/app/details/triggerView/TriggerView.tsx index 3842c9fa4e..45a84911db 100644 --- a/src/components/app/details/triggerView/TriggerView.tsx +++ b/src/components/app/details/triggerView/TriggerView.tsx @@ -224,6 +224,7 @@ const TriggerView = ({ isJobView, filteredEnvIds }: TriggerViewProps) => { workflows={workflows} handleClose={revertToPreviousURL} handleSuccess={reloadWorkflowStatus} + isTriggerView /> {renderApprovalMaterial()}
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts index 0289ba24c8..e4b887ec8a 100644 --- a/src/components/app/details/triggerView/types.ts +++ b/src/components/app/details/triggerView/types.ts @@ -583,4 +583,5 @@ export interface GetCDNodeSearchParams { export interface CDMaterialProps extends Pick { workflows: WorkflowType[] + isTriggerView: boolean } From 59e4222b3c4115af5673d0c605c3760072d106f9 Mon Sep 17 00:00:00 2001 From: AbhishekA1509 Date: Tue, 12 Aug 2025 15:15:10 +0530 Subject: [PATCH 9/9] Add appName prop to Details component for enhanced app information --- src/components/app/details/appDetails/AppDetails.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/app/details/appDetails/AppDetails.tsx b/src/components/app/details/appDetails/AppDetails.tsx index 48f6c4a58d..b89913da53 100644 --- a/src/components/app/details/appDetails/AppDetails.tsx +++ b/src/components/app/details/appDetails/AppDetails.tsx @@ -591,6 +591,7 @@ const Details: React.FC = ({ environmentId={appDetails.environmentId} environmentName={appDetails.environmentName} isVirtualEnvironment={appDetails.isVirtualEnvironment} + appName={appDetails.appName} deploymentAppType={appDetails.deploymentAppType} cdModal={{ cdPipelineId: appDetails.cdPipelineId,