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/package.json b/package.json
index dd849f2b5d..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-2",
+ "@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/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..83be81921b 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,8 @@ 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 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'
@@ -58,7 +58,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,14 +70,12 @@ 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'
@@ -492,7 +490,7 @@ const EnvTriggerView = ({ filteredAppIds, isVirtualEnv }: AppGroupDetailDefaultT
workflows={filteredWorkflows}
isVirtualEnvironment={isVirtualEnv}
envId={+envId}
- handleSuccess={reloadTriggerView}
+ handleSuccess={getWorkflowStatusData}
/>
)
}
@@ -529,95 +527,18 @@ 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/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 (
-
+
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/appDetails/AppDetails.tsx b/src/components/app/details/appDetails/AppDetails.tsx
index 06f86b4e96..b89913da53 100644
--- a/src/components/app/details/appDetails/AppDetails.tsx
+++ b/src/components/app/details/appDetails/AppDetails.tsx
@@ -591,8 +591,8 @@ const Details: React.FC
= ({
environmentId={appDetails.environmentId}
environmentName={appDetails.environmentName}
isVirtualEnvironment={appDetails.isVirtualEnvironment}
+ appName={appDetails.appName}
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) && (
{
+ const location = useLocation()
+ const { currentAppName: triggerViewAppName } = useAppContext()
+
+ if (
+ location.search.includes(TRIGGER_VIEW_PARAMS.CD_NODE) ||
+ location.search.includes(TRIGGER_VIEW_PARAMS.ROLLBACK_NODE)
+ ) {
+ 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 === cdNodeId))
+ const selectedCINode = selectedWorkflow?.nodes.find((node) => node.type === 'CI' || node.type === 'WEBHOOK')
+ const doesWorkflowContainsWebhook = selectedCINode?.type === 'WEBHOOK'
+
+ const appId = selectedWorkflow?.appId ?? 0
+
+ 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/DeployImageModal/BulkDeployEmptyState.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkDeployEmptyState.tsx
index e9803bebf7..e39fd9fa13 100644
--- a/src/components/app/details/triggerView/DeployImageModal/BulkDeployEmptyState.tsx
+++ b/src/components/app/details/triggerView/DeployImageModal/BulkDeployEmptyState.tsx
@@ -52,7 +52,7 @@ const BulkDeployEmptyState = ({
return (
)
diff --git a/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx b/src/components/app/details/triggerView/DeployImageModal/BulkDeployModal.tsx
index 4c798ac8f9..e08388074a 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,11 +8,13 @@ import {
ButtonStyleType,
CDMaterialResponseType,
CDMaterialServiceEnum,
+ DEFAULT_ROUTE_PROMPT_MESSAGE,
DeploymentNodeType,
DeploymentStrategyTypeWithDefault,
Drawer,
genericCDMaterialsService,
GenericEmptyState,
+ getIsApprovalPolicyConfigured,
Icon,
MODAL_TYPE,
ModuleNameMap,
@@ -27,6 +30,7 @@ import {
uploadCDPipelineFile,
useAsync,
useMainContext,
+ usePrompt,
} from '@devtron-labs/devtron-fe-common-lib'
import { ResponseRowType } from '@Components/ApplicationGroup/AppGroup.types'
@@ -97,6 +101,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 +145,7 @@ const BulkDeployModal = ({
(node) => node.type === stageType && +node.environmentId === +envId,
)
- if (!currentStageNode) {
+ if (!currentStageNode || baseBulkCDDetailMap[workflow.appId].errorMessage) {
return () => null
}
@@ -167,7 +173,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 +229,8 @@ const BulkDeployModal = ({
},
}))
- const { deploymentWindowMetadata, materialError, warningMessage } = response[selectedAppId] || {}
+ const { deploymentWindowMetadata, materialError, errorMessage, tagsWarningMessage } =
+ response[selectedAppId] || {}
if (materialError) {
showError(materialError)
@@ -243,7 +250,8 @@ const BulkDeployModal = ({
[selectedAppId]: {
...prev[selectedAppId],
materialResponse: response[selectedAppId]?.materialResponse,
- warningMessage,
+ errorMessage,
+ tagsWarningMessage,
materialError,
deploymentWindowMetadata,
deployViewState: {
@@ -385,8 +393,6 @@ const BulkDeployModal = ({
return
}
- setIsDeploymentLoading(true)
-
const { cdTriggerPromiseFunctions, triggeredAppIds } = getTriggerCDPromiseMethods({
appInfoMap,
appsToRetry,
@@ -396,11 +402,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 +427,7 @@ const BulkDeployModal = ({
setResponseList(newResponseList)
setIsDeploymentLoading(false)
+ setNumberOfAppsLoading(0)
}
const setDeployViewState: DeployImageContentProps['setDeployViewState'] = (getUpdatedDeployViewState) => {
@@ -469,11 +479,9 @@ const BulkDeployModal = ({
const { tagsWarning, updatedMaterials } = getUpdatedMaterialsForTagSelection(
tagOption.value,
appDetails.materialResponse?.materials || [],
- )
-
- const { tagsWarning: previousTagWarning } = getUpdatedMaterialsForTagSelection(
- selectedImageTagOption.value,
- appDetails.materialResponse?.materials || [],
+ !getIsApprovalPolicyConfigured(
+ appDetails.materialResponse?.deploymentApprovalInfo?.approvalConfigData,
+ ) || getIsExceptionUser(appDetails.materialResponse),
)
updatedAppInfoMap[appDetails.appId] = {
@@ -482,8 +490,7 @@ const BulkDeployModal = ({
...appDetails.materialResponse,
materials: updatedMaterials,
},
- warningMessage:
- previousTagWarning || !appDetails.warningMessage ? tagsWarning : appDetails.warningMessage,
+ tagsWarningMessage: tagsWarning,
}
})
return updatedAppInfoMap
@@ -502,16 +509,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 +660,7 @@ const BulkDeployModal = ({
handleTagChange={handleTagChange}
changeApp={changeApp}
selectedTagName={selectedImageTagOption.value}
+ onImageSelection={onImageSelection}
/>
)
}
@@ -682,7 +711,7 @@ const BulkDeployModal = ({
onButtonClick={onClickStartDeploy}
disabled={isDeployButtonDisabled}
isLoading={isDeploymentLoading}
- animateStartIcon={isCDStage}
+ animateStartIcon={isCDStage && !isDeployButtonDisabled}
style={
canDeployWithoutApproval || canImageApproverDeploy
? ButtonStyleType.warning
@@ -701,36 +730,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 97d7466ae0..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,
@@ -246,8 +247,6 @@ const processConsumedAndApprovedImages = (materials: CDMaterialType[]) => {
!mat.userApprovalMetadata ||
mat.userApprovalMetadata.approvalRuntimeState !== ApprovalRuntimeStateType.approved
) {
- // TODO: Check if this is needed
- // mat.isSelected = false
consumedImage.push(mat)
} else {
approvedImages.push(mat)
@@ -383,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 =
@@ -401,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) => {
@@ -440,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
@@ -497,7 +512,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,
@@ -524,32 +539,41 @@ export const getBulkCDDetailsMapFromResponse: GetBulkCDDetailsMapFromResponseTyp
const { tagsWarning, updatedMaterials } = getUpdatedMaterialsForTagSelection(
selectedTagName,
materialResponse.value.materials,
+ !getIsApprovalPolicyConfigured(materialResponse?.value.deploymentApprovalInfo?.approvalConfigData) ||
+ getIsExceptionUser(materialResponse.value),
)
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 0496bd9976..45a84911db 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,76 +99,20 @@ 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)
- if (!cdNode.id) {
- return null
- }
- 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,
- doesWorkflowContainsWebhook ? '0' : selectedCINode?.id,
- doesWorkflowContainsWebhook,
- cdNode.id,
- 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
- }
+ const { node, cdNodeId } = getSelectedNodeFromWorkflows(workflows, location.search)
return (
)
@@ -287,7 +220,12 @@ 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 e552a172dd..32c9957153 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'
@@ -212,24 +211,23 @@ 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) {
- showError('Invalid node id')
- 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 { cdNodeId, node: foundNode }
+ }
}
- showError('Invalid node id')
- return {} as CommonNodeAttr
+ return { cdNodeId: cdNodeId ?? '0', node: {} as CommonNodeAttr }
}
export const getCDNodeActionSearch = ({
diff --git a/src/components/app/details/triggerView/types.ts b/src/components/app/details/triggerView/types.ts
index 2f075ec190..e4b887ec8a 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,8 @@ export interface GetCDNodeSearchParams {
nodeType?: DeploymentNodeType
fromAppGroup?: boolean
}
+
+export interface CDMaterialProps extends Pick {
+ workflows: WorkflowType[]
+ isTriggerView: boolean
+}
diff --git a/src/components/app/types.ts b/src/components/app/types.ts
index 84f2c699c8..61299aeb1d 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
@@ -535,13 +534,12 @@ export interface SourceInfoType extends Pick, 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']
+ handleSuccess?: DeployImageModalProps['handleSuccess']
materialType: DeployImageModalProps['materialType']
closeCDModal: () => void
}
diff --git a/yarn.lock b/yarn.lock
index 88b83efba9..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-2":
- version: 1.19.0-pre-2
- resolution: "@devtron-labs/devtron-fe-common-lib@npm:1.19.0-pre-2"
+"@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/fffdd3524d59a4c19206ca9454b3234010c80eb2b2861b792c6a1b20e66a9e9fd3c21b4a07cec811b9ee733420768efb08d782c901db416e8dd3e27f7dd5103e
+ 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-2"
+ "@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"