Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mandatory tags blocks trigger #2246

Merged
merged 45 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
1c7c3f0
feat: integrate api changes for mandatory tags on cd modal and materi…
arunjaindev Dec 2, 2024
81c4320
chore: version bump
arunjaindev Dec 2, 2024
aa5e421
feat: set warning message for buld cd trigger
arunjaindev Dec 3, 2024
48316c4
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 4, 2024
00c6b68
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 4, 2024
ed0946f
chore: version bump to 1.2.2-beta-2
shivani170 Dec 4, 2024
a6b3026
chore: remove this later
shivani170 Dec 5, 2024
78742f6
feat: use dynamic data table for mandatory and
arunjaindev Dec 5, 2024
9759ae8
feat: add support for configured tags
arunjaindev Dec 5, 2024
878c4bc
chore: move policy consequences api into promise.all
arunjaindev Dec 5, 2024
f467956
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 5, 2024
321ba55
chore: version bump
arunjaindev Dec 5, 2024
eb7d13c
Merge branch 'feat/mandatory-tags-v2' into feat/mandatory-tags-cd
shivani170 Dec 6, 2024
6ce7f5d
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 9, 2024
ba88bcb
chore: extend trigger cd node type
arunjaindev Dec 9, 2024
a63a0dc
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 9, 2024
1a313a7
chore: version bump
arunjaindev Dec 9, 2024
1db5da9
fix: pushing labels twice
arunjaindev Dec 9, 2024
08aed2e
fix: ignore empty rows
arunjaindev Dec 9, 2024
9d1e090
fix: update return statement for blovk state
arunjaindev Dec 9, 2024
583a8ef
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 9, 2024
0164408
fix: show auto trigger block reason info
arunjaindev Dec 9, 2024
cb714b7
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 9, 2024
2ce92a5
fix: page break if response list not available
arunjaindev Dec 10, 2024
97ec665
chore: extract promise in cd material
arunjaindev Dec 10, 2024
6bc129e
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 10, 2024
998ed46
chore: use common util for class and heading
arunjaindev Dec 10, 2024
c5e233b
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 10, 2024
5b8c83e
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 12, 2024
d82939e
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 12, 2024
3dbc362
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 15, 2024
6c619a1
Merge branch 'feat/mandatory-tags-cd' of https://github.com/devtron-l…
arunjaindev Dec 15, 2024
0b66722
feat: add validations in tags table
arunjaindev Dec 16, 2024
9a6ca73
Merge pull request #2266 from devtron-labs/feat/create-edit-app-tags
arunjaindev Dec 17, 2024
722c9b6
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 17, 2024
a70d501
chore: add validations and GA events
arunjaindev Dec 17, 2024
c78732f
chore: add appType for GA EVENT
arunjaindev Dec 17, 2024
efe76b0
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 17, 2024
e859de4
chore: version bump
arunjaindev Dec 18, 2024
5c37f47
Merge branch 'main' of https://github.com/devtron-labs/dashboard into…
arunjaindev Dec 19, 2024
6892013
chore: version bump
arunjaindev Dec 19, 2024
c041503
fix: validation errors on tags
arunjaindev Dec 19, 2024
26a1ff3
fix: error highlighting in tags table
arunjaindev Dec 19, 2024
6d28aa0
Merge branch 'main' of https://github.com/devtron-labs/dashboard into…
arunjaindev Dec 19, 2024
bc72bbe
Merge branch 'feat/mandatory-tags-v2' of https://github.com/devtron-l…
arunjaindev Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"homepage": "/dashboard",
"dependencies": {
"@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-2",
"@devtron-labs/devtron-fe-common-lib": "1.2.4-beta-3",
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
"@rjsf/core": "^5.13.3",
"@rjsf/utils": "^5.13.3",
Expand Down
9 changes: 8 additions & 1 deletion src/components/ApplicationGroup/AppGroup.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface BulkCDDetailTypeResponse {
export interface BulkCDDetailType
extends BulkTriggerAppDetailType,
Pick<CDMaterialProps, 'isTriggerBlockedDueToPlugin' | 'configurePluginURL' | 'consequence'>,
Partial<Pick<CommonNodeAttr, 'showPluginWarning'>> {
Partial<Pick<CommonNodeAttr, 'showPluginWarning' | 'triggerBlockedInfo'>> {
cdPipelineName?: string
cdPipelineId?: string
stageType?: DeploymentNodeType
Expand Down Expand Up @@ -613,4 +613,11 @@ export enum AppGroupUrlFilters {

export interface AppGroupUrlFiltersType extends Record<AppGroupUrlFilters, string[]> {}

export interface SetFiltersInLocalStorageParamsType {
filterParentType: FilterParentType
resourceId: string
resourceList: MultiValue<OptionType>
groupList: MultiValue<GroupOptionType>
}

export type AppEnvLocalStorageKeyType = `${string}__filter`
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,22 @@ import {
ToastManager,
ToastVariantType,
CommonNodeAttr,
TriggerBlockType,
} from '@devtron-labs/devtron-fe-common-lib'
import { useHistory, useLocation } from 'react-router-dom'
import { ReactComponent as Close } from '../../../../assets/icons/ic-cross.svg'
import { ReactComponent as DeployIcon } from '../../../../assets/icons/ic-nav-rocket.svg'
import { ReactComponent as PlayIcon } from '../../../../assets/icons/ic-play-medium.svg'
import { ReactComponent as Error } from '../../../../assets/icons/ic-warning.svg'
import { ReactComponent as UnAuthorized } from '../../../../assets/icons/ic-locked.svg'
import { ReactComponent as Tag } from '../../../../assets/icons/ic-tag.svg'
import { ReactComponent as Close } from '@Icons/ic-cross.svg'
import { ReactComponent as DeployIcon } from '@Icons/ic-nav-rocket.svg'
import { ReactComponent as PlayIcon } from '@Icons/ic-play-medium.svg'
import { ReactComponent as Error } from '@Icons/ic-warning.svg'
import { ReactComponent as UnAuthorized } from '@Icons/ic-locked.svg'
import { ReactComponent as Tag } from '@Icons/ic-tag.svg'
import emptyPreDeploy from '../../../../assets/img/empty-pre-deploy.png'
import notAuthorized from '../../../../assets/img/ic-not-authorized.svg'
import CDMaterial from '../../../app/details/triggerView/cdMaterial'
import { BulkSelectionEvents, MATERIAL_TYPE } from '../../../app/details/triggerView/types'
import { BulkCDDetailType, BulkCDTriggerType } from '../../AppGroup.types'
import { BULK_CD_DEPLOYMENT_STATUS, BULK_CD_MATERIAL_STATUS, BULK_CD_MESSAGING, BUTTON_TITLE } from '../../Constants'
import TriggerResponseModal from './TriggerResponseModal'
import { EmptyView } from '../../../app/details/cicdHistory/History.components'
import { ReactComponent as MechanicalOperation } from '../../../../assets/img/ic-mechanical-operation.svg'
import { importComponentFromFELibrary } from '../../../common'
import { BULK_ERROR_MESSAGES } from './constants'
Expand All @@ -77,6 +77,8 @@ const getDeploymentWindowStateAppGroup = importComponentFromFELibrary(
const RuntimeParamTabs = importComponentFromFELibrary('RuntimeParamTabs', null, 'function')
const MissingPluginBlockState = importComponentFromFELibrary('MissingPluginBlockState', null, 'function')
const PolicyEnforcementMessage = importComponentFromFELibrary('PolicyEnforcementMessage')
const TriggerBlockedError = importComponentFromFELibrary('TriggerBlockedError', null, 'function')
const TriggerBlockEmptyState = importComponentFromFELibrary('TriggerBlockEmptyState', null, 'function')

// TODO: Fix release tags selection
export default function BulkCDTrigger({
Expand Down Expand Up @@ -379,6 +381,10 @@ export default function BulkCDTrigger({
}

const renderEmptyView = (): JSX.Element => {
if (selectedApp.triggerBlockedInfo?.blockedBy === TriggerBlockType.MANDATORY_TAG) {
return <TriggerBlockEmptyState stageType={selectedApp.stageType} appId={selectedApp.appId} />
}

if (selectedApp.isTriggerBlockedDueToPlugin) {
const commonNodeAttrType: CommonNodeAttr['type'] =
selectedApp.stageType === DeploymentNodeType.PRECD ? 'PRECD' : 'POSTCD'
Expand All @@ -393,16 +399,16 @@ export default function BulkCDTrigger({

if (unauthorizedAppList[selectedApp.appId]) {
return (
<EmptyView
imgSrc={notAuthorized}
<GenericEmptyState
image={notAuthorized}
title={BULK_CD_MESSAGING.unauthorized.title}
subTitle={BULK_CD_MESSAGING.unauthorized.subTitle}
/>
)
}
return (
<EmptyView
imgSrc={emptyPreDeploy}
<GenericEmptyState
image={emptyPreDeploy}
title={`${selectedApp.name} ${BULK_CD_MESSAGING[stage].title}`}
subTitle={BULK_CD_MESSAGING[stage].subTitle}
/>
Expand All @@ -426,16 +432,16 @@ export default function BulkCDTrigger({
)
}

if (app.triggerBlockedInfo?.blockedBy === TriggerBlockType.MANDATORY_TAG) {
return <TriggerBlockedError stageType={app.stageType} />
}

if (tagNotFoundWarningsMap.has(app.appId)) {
return (
<div className="flex left top dc__gap-4">
<Error
className="icon-dim-12 dc__no-shrink mt-5 alert-icon-r5-imp"
/>
<Error className="icon-dim-12 dc__no-shrink mt-5 alert-icon-r5-imp" />

<span className="fw-4 fs-12 cr-5 dc__truncate">
{tagNotFoundWarningsMap.get(app.appId)}
</span>
<span className="fw-4 fs-12 cr-5 dc__truncate">{tagNotFoundWarningsMap.get(app.appId)}</span>
</div>
)
}
Expand All @@ -454,13 +460,8 @@ export default function BulkCDTrigger({
if (!!warningMessage && !app.showPluginWarning) {
return (
<div className="flex left top dc__gap-4">
<Error
className="icon-dim-12 dc__no-shrink mt-5 warning-icon-y7"
/>

<span className="fw-4 fs-12 cy-7 dc__truncate">
{warningMessage}
</span>
<Error className="icon-dim-12 dc__no-shrink mt-5 warning-icon-y7" />
<span className="fw-4 fs-12 cy-7 dc__truncate">{warningMessage}</span>
</div>
)
}
Expand All @@ -473,7 +474,7 @@ export default function BulkCDTrigger({
nodeType={commonNodeAttrType}
shouldRenderAdditionalInfo={isAppSelected}
/>
)
)
}

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import {
ToastManager,
ToastVariantType,
BlockedStateData,
getStageTitle,
TriggerBlockType,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
Expand Down Expand Up @@ -1751,7 +1753,10 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
const stageType = DeploymentNodeType[_selectedNode.type]
const isTriggerBlockedDueToPlugin =
_selectedNode.isTriggerBlocked && _selectedNode.showPluginWarning
const stageText = stageType === DeploymentNodeType.PRECD ? 'Pre-Deployment' : 'Post-Deployment'
const isTriggerBlockedDueToMandatoryTags =
_selectedNode.isTriggerBlocked &&
_selectedNode.triggerBlockedInfo?.blockedBy === TriggerBlockType.MANDATORY_TAG
const stageText = getStageTitle(stageType)

_selectedAppWorkflowList.push({
workFlowId: wf.id,
Expand Down Expand Up @@ -1785,7 +1790,11 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
true,
),
consequence: _selectedNode.pluginBlockState,
warningMessage: isTriggerBlockedDueToPlugin ? `${stageText} is blocked` : '',
warningMessage:
isTriggerBlockedDueToPlugin || isTriggerBlockedDueToMandatoryTags
? `${stageText} is blocked`
: '',
triggerBlockedInfo: _selectedNode.triggerBlockedInfo,
})
} else {
let warningMessage = ''
Expand Down Expand Up @@ -1982,9 +1991,7 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
if (selectedCINode?.id) {
return (
<Switch>
<Route
path={`${url}${URLS.BUILD}/:ciNodeId/${URLS.WEBHOOK_MODAL}`}
>
<Route path={`${url}${URLS.BUILD}/:ciNodeId/${URLS.WEBHOOK_MODAL}`}>
<WebhookReceivedPayloadModal
workflowId={workflowID}
webhookPayloads={webhookPayloads}
Expand Down
6 changes: 1 addition & 5 deletions src/components/app/details/triggerView/TriggerView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,6 @@ class TriggerView extends Component<TriggerViewProps, TriggerViewState> {
this.getWorkflowStatus()
}


onClickWebhookTimeStamp = () => {
if (this.state.webhookTimeStampOrder === TIME_STAMP_ORDER.DESCENDING) {
this.setState({ webhookTimeStampOrder: TIME_STAMP_ORDER.ASCENDING })
Expand Down Expand Up @@ -1172,16 +1171,13 @@ class TriggerView extends Component<TriggerViewProps, TriggerViewState> {
this.abortCIBuild = new AbortController()
}


renderCIMaterial = () => {
if (this.state.ciNodeId) {
const nd: CommonNodeAttr = this.getCINode()
const material = nd?.[this.state.materialType] || []
return (
<Switch>
<Route
path={`${this.props.match.url}${URLS.BUILD}/:ciNodeId/${URLS.WEBHOOK_MODAL}`}
>
<Route path={`${this.props.match.url}${URLS.BUILD}/:ciNodeId/${URLS.WEBHOOK_MODAL}`}>
<WebhookReceivedPayloadModal
workflowId={this.state.workflowId}
webhookPayloads={this.state.webhookPayloads}
Expand Down
110 changes: 77 additions & 33 deletions src/components/app/details/triggerView/cdMaterial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ import {
ResponseType,
ApiResponseResultType,
CommonNodeAttr,
GetPolicyConsequencesProps,
PolicyConsequencesDTO,
PipelineStageBlockInfo,
} from '@devtron-labs/devtron-fe-common-lib'
import Tippy from '@tippyjs/react'
import {
Expand Down Expand Up @@ -139,7 +142,6 @@ const getIsImageApproverFromUserApprovalMetaData: (
email: string,
userApprovalMetadata: UserApprovalMetadataType,
) => boolean = importComponentFromFELibrary('getIsImageApproverFromUserApprovalMetaData', () => false, 'function')
const isFELibAvailable = importComponentFromFELibrary('isFELibAvailable', null, 'function')
const getSecurityScan: ({
appId,
envId,
Expand All @@ -152,6 +154,9 @@ const getSecurityScan: ({
const SecurityModalSidebar = importComponentFromFELibrary('SecurityModalSidebar', null, 'function')
const AllowedWithWarningTippy = importComponentFromFELibrary('AllowedWithWarningTippy')
const MissingPluginBlockState = importComponentFromFELibrary('MissingPluginBlockState', null, 'function')
const TriggerBlockEmptyState = importComponentFromFELibrary('TriggerBlockEmptyState', null, 'function')
const getPolicyConsequences: ({ appId, envId }: GetPolicyConsequencesProps) => Promise<PolicyConsequencesDTO> =
importComponentFromFELibrary('getPolicyConsequences', null, 'function')

const CDMaterial = ({
materialType,
Expand Down Expand Up @@ -219,38 +224,61 @@ const CDMaterial = ({
const allowWarningWithTippyNodeTypeProp: CommonNodeAttr['type'] =
stageType === DeploymentNodeType.PRECD ? 'PRECD' : 'POSTCD'

// this function can be used for other trigger block reasons once api supports it
const getIsTriggerBlocked = (cdPolicyConsequences: PipelineStageBlockInfo) => {
switch (stageType) {
case DeploymentNodeType.PRECD:
return cdPolicyConsequences.pre.isBlocked
case DeploymentNodeType.POSTCD:
return cdPolicyConsequences.post.isBlocked
case DeploymentNodeType.CD:
return cdPolicyConsequences.node.isBlocked
default:
return false
}
}

const getMaterialResponseList = async(): Promise<[CDMaterialResponseType, any, PolicyConsequencesDTO]> => {
const response = await Promise.all([
genericCDMaterialsService(
materialType === MATERIAL_TYPE.rollbackMaterialList
? CDMaterialServiceEnum.ROLLBACK
: CDMaterialServiceEnum.CD_MATERIALS,
pipelineId,
// Don't think need to set stageType to approval in case of approval node
stageType ?? DeploymentNodeType.CD,
abortControllerRef.current.signal,
// It is meant to fetch the first 20 materials
{
offset: 0,
size: 20,
search: searchImageTag,
// Since by default we are setting filterView to eligible and in case of no filters everything is eligible
// So there should'nt be any additional api call
// NOTE: Uncomment this when backend supports the filtering, there will be some minor handling like number of images in segmented control
// filter:
// state.filterView === FilterConditionViews.ELIGIBLE && !state.searchApplied
// ? CDMaterialFilterQuery.RESOURCE
// : null,
},
),
getDeploymentWindowProfileMetaData && !isFromBulkCD
? getDeploymentWindowProfileMetaData(appId, envId)
: null,
getPolicyConsequences ? getPolicyConsequences({ appId, envId }) : null,
])

if (getIsTriggerBlocked(response[2].cd)) {
return [null, null, response[2]]
}
return response
}

// TODO: Ask if pipelineId always changes on change of app else add appId as dependency
const [loadingMaterials, responseList, materialsError, reloadMaterials] = useAsync(
() =>
abortPreviousRequests(
() =>
Promise.all([
genericCDMaterialsService(
materialType === MATERIAL_TYPE.rollbackMaterialList
? CDMaterialServiceEnum.ROLLBACK
: CDMaterialServiceEnum.CD_MATERIALS,
pipelineId,
// Don't think need to set stageType to approval in case of approval node
stageType ?? DeploymentNodeType.CD,
abortControllerRef.current.signal,
// It is meant to fetch the first 20 materials
{
offset: 0,
size: 20,
search: searchImageTag,
// Since by default we are setting filterView to eligible and in case of no filters everything is eligible
// So there should'nt be any additional api call
// NOTE: Uncomment this when backend supports the filtering, there will be some minor handling like number of images in segmented control
// filter:
// state.filterView === FilterConditionViews.ELIGIBLE && !state.searchApplied
// ? CDMaterialFilterQuery.RESOURCE
// : null,
},
),
getDeploymentWindowProfileMetaData && !isFromBulkCD
? getDeploymentWindowProfileMetaData(appId, envId)
: null,
]),
() => getMaterialResponseList(),
abortControllerRef,
),
// NOTE: Add state.filterView if want to add filtering support from backend
Expand Down Expand Up @@ -1163,7 +1191,9 @@ const CDMaterial = ({
const _gitCommit = getGitCommitInfo(mat)

if (
(materialData.appliedFilters?.length > 0 || materialData.deploymentWindowArtifactMetadata?.type) &&
(materialData.appliedFilters?.length > 0 ||
materialData.deploymentBlockedState?.isBlocked ||
materialData.deploymentWindowArtifactMetadata?.type) &&
CDMaterialInfo
) {
return (
Expand All @@ -1177,6 +1207,7 @@ const CDMaterial = ({
dataSource={materialData.dataSource}
deploymentWindowArtifactMetadata={materialData.deploymentWindowArtifactMetadata}
isFilterApplied={materialData.appliedFilters?.length > 0}
triggerBlockedInfo={materialData.deploymentBlockedState}
>
{(_gitCommit.WebhookData?.Data ||
_gitCommit.Author ||
Expand Down Expand Up @@ -1713,8 +1744,7 @@ const CDMaterial = ({
</Tippy>
)}
>
{AllowedWithWarningTippy &&
showPluginWarningBeforeTrigger ? (
{AllowedWithWarningTippy && showPluginWarningBeforeTrigger ? (
<AllowedWithWarningTippy
consequence={consequence}
configurePluginURL={configurePluginURL}
Expand Down Expand Up @@ -1914,6 +1944,20 @@ const CDMaterial = ({
)
}

if (TriggerBlockEmptyState && getIsTriggerBlocked(responseList[2].cd)) {
return (
<>
<div className="trigger-modal__header">
<h2 className="modal__title">{renderCDModalHeader()}</h2>
<button type="button" className="dc__transparent" onClick={closeCDModal}>
<img alt="close" src={close} />
</button>
</div>
<TriggerBlockEmptyState appId={appId} stageType={stageType} />
</>
)
}

if (material.length > 0) {
return isFromBulkCD ? (
<>
Expand All @@ -1934,7 +1978,7 @@ const CDMaterial = ({
return (
<>
<div className="trigger-modal__header">
<h1 className="modal__title">{renderCDModalHeader()}</h1>
<h2 className="modal__title">{renderCDModalHeader()}</h2>
<button type="button" className="dc__transparent" onClick={closeCDModal}>
<img alt="close" src={close} />
</button>
Expand Down
Loading
Loading