Skip to content

Commit 7e8fac9

Browse files
authored
Merge pull request #2028 from devtron-labs/refactor/application-group-overview-table-phase-2
EnvironmentOverview - add support for individual app action
2 parents bbfba16 + 6e4a5aa commit 7e8fac9

File tree

8 files changed

+153
-76
lines changed

8 files changed

+153
-76
lines changed

src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.component.tsx

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { ChangeEvent, Fragment, useMemo, useState } from 'react'
22
import { Link } from 'react-router-dom'
3-
import Tippy from '@tippyjs/react'
4-
import { followCursor } from 'tippy.js'
53

64
import {
75
AppStatus,
@@ -16,6 +14,7 @@ import {
1614
PopupMenu,
1715
stringComparatorBySortOrder,
1816
handleRelativeDateSorting,
17+
Tooltip,
1918
} from '@devtron-labs/devtron-fe-common-lib'
2019

2120
import { ReactComponent as DevtronIcon } from '@Icons/ic-devtron-app.svg'
@@ -42,23 +41,31 @@ const renderPopUpMenu = (items: EnvironmentOverviewTableRow['popUpMenuItems']) =
4241
<PopupMenu.Button isKebab rootClassName="p-4 flex dc__no-border cursor">
4342
<ICMoreOption className="icon-dim-16 fcn-6 rotateBy--90" />
4443
</PopupMenu.Button>
45-
<PopupMenu.Body rootClassName="dc__border py-4 w-150">
46-
{items.map(({ label, onClick, disabled, Icon, iconType = 'fill' }) => (
47-
<button
48-
key={label}
49-
type="button"
50-
className="dc__transparent w-100 py-6 px-8 flexbox dc__align-items-center dc__gap-8 dc__hover-n50"
51-
onClick={onClick}
52-
disabled={disabled}
53-
>
54-
{Icon && (
55-
<Icon
56-
className={`icon-dim-16 ${iconType === 'fill' ? 'fcn-7' : ''} ${iconType === 'stroke' ? 'scn-7' : ''}`}
57-
/>
58-
)}
59-
<span className="dc__truncate cn-9 fs-13 lh-20">{label}</span>
60-
</button>
61-
))}
44+
<PopupMenu.Body rootClassName="dc__border py-4 w-180">
45+
{items.map((popUpMenuItem) => {
46+
if ('label' in popUpMenuItem) {
47+
const { label, onClick, disabled, Icon, iconType = 'fill' } = popUpMenuItem
48+
49+
return (
50+
<button
51+
key={label}
52+
type="button"
53+
className={`dc__transparent w-100 py-6 px-8 flexbox dc__align-items-center dc__gap-8 ${disabled ? ' dc__opacity-0_5 cursor-not-allowed' : 'dc__hover-n50'}`}
54+
onClick={onClick}
55+
disabled={disabled}
56+
>
57+
{Icon && (
58+
<Icon
59+
className={`icon-dim-16 ${iconType === 'fill' ? 'fcn-7' : ''} ${iconType === 'stroke' ? 'scn-7' : ''}`}
60+
/>
61+
)}
62+
<span className="dc__truncate cn-9 fs-13 lh-20">{label}</span>
63+
</button>
64+
)
65+
}
66+
67+
return popUpMenuItem
68+
})}
6269
</PopupMenu.Body>
6370
</PopupMenu>
6471
)
@@ -199,25 +206,23 @@ export const EnvironmentOverviewTable = ({
199206
value="CHECKED"
200207
rootClassName={`mb-0 ml-2 ${!isPartialChecked ? 'dc__visible-hover--child' : ''}`}
201208
/>
202-
{!isVirtualEnv && <AppStatus appStatus={status} hideStatusMessage />}
209+
{!isVirtualEnv && (
210+
<AppStatus
211+
appStatus={deployedAt ? status : StatusConstants.NOT_DEPLOYED.noSpaceLower}
212+
hideStatusMessage
213+
/>
214+
)}
203215
<div className="flexbox dc__align-items-center dc__content-space dc__gap-8">
204-
<Tippy
205-
className="default-tt"
206-
arrow={false}
207-
placement="bottom"
208-
content={name}
209-
followCursor="horizontal"
210-
plugins={[followCursor]}
211-
>
216+
<Tooltip content={name}>
212217
<Link
213-
className="py-2 dc__truncate flex-grow-1"
218+
className="py-2 dc__truncate dc__no-decor"
214219
to={redirectLink}
215220
target="_blank"
216221
rel="noreferrer"
217222
>
218223
{name}
219224
</Link>
220-
</Tippy>
225+
</Tooltip>
221226
{!!popUpMenuItems?.length && renderPopUpMenu(popUpMenuItems)}
222227
</div>
223228
</div>
@@ -250,9 +255,9 @@ export const EnvironmentOverviewTable = ({
250255
>
251256
{deployedBy[0]}
252257
</span>
253-
<Tippy className="default-tt" arrow={false} placement="bottom" content={deployedBy}>
258+
<Tooltip content={deployedBy}>
254259
<span className="dc__truncate">{deployedBy}</span>
255-
</Tippy>
260+
</Tooltip>
256261
</div>
257262
</>
258263
)}

src/Pages/Shared/EnvironmentOverviewTable/EnvironmentOverviewTable.types.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FunctionComponent, MouseEvent, SVGProps } from 'react'
1+
import { FunctionComponent, MouseEvent, ReactElement, SVGProps } from 'react'
22
import { EnvironmentOverviewTableHeaderKeys } from './EnvironmentOverview.constants'
33

44
export interface EnvironmentOverviewTableRowData {
@@ -12,13 +12,15 @@ export interface EnvironmentOverviewTableRowData {
1212
[EnvironmentOverviewTableHeaderKeys.DEPLOYED_BY]: string
1313
}
1414

15-
export interface EnvironmentOverviewTablePopUpMenuItem {
16-
label: string
17-
Icon?: FunctionComponent<SVGProps<SVGSVGElement>>
18-
iconType?: 'fill' | 'stroke'
19-
disabled?: boolean
20-
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
21-
}
15+
export type EnvironmentOverviewTablePopUpMenuItem =
16+
| {
17+
label: string
18+
Icon?: FunctionComponent<SVGProps<SVGSVGElement>>
19+
iconType?: 'fill' | 'stroke'
20+
disabled?: boolean
21+
onClick?: (event: MouseEvent<HTMLButtonElement>) => void
22+
}
23+
| ReactElement
2224

2325
export interface EnvironmentOverviewTableRow {
2426
environment: EnvironmentOverviewTableRowData
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './EnvironmentOverviewTable.component'
22
export * from './EnvironmentOverviewTable.types'
3+
export { EnvironmentOverviewTableSortableKeys } from './EnvironmentOverview.constants'

src/components/ApplicationGroup/AppGroup.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ type HibernateModalType = MODAL_TYPE.HIBERNATE | MODAL_TYPE.UNHIBERNATE
473473

474474
export interface HibernateModalProps {
475475
setOpenedHibernateModalType: React.Dispatch<React.SetStateAction<HibernateModalType>>
476-
selectedAppDetailsList: AppInfoListType[]
476+
selectedAppDetailsList: AppInfoListType | AppInfoListType[]
477477
appDetailsList: AppGroupListType['apps']
478478
envName: string
479479
envId: string
@@ -502,7 +502,7 @@ export interface ManageAppsResponse {
502502
}
503503

504504
export interface RestartWorkloadModalProps {
505-
selectedAppDetailsList: AppInfoListType[]
505+
selectedAppDetailsList: AppInfoListType | AppInfoListType[]
506506
envName: string
507507
envId: string
508508
restartLoader: boolean

src/components/ApplicationGroup/Details/EnvironmentOverview/EnvironmentOverview.tsx

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { ReactComponent as HibernateIcon } from '../../../../assets/icons/ic-hib
5353
import { ReactComponent as UnhibernateIcon } from '../../../../assets/icons/ic-unhibernate.svg'
5454
import { ReactComponent as RotateIcon } from '../../../../assets/icons/ic-arrows_clockwise.svg'
5555
import { renderCIListHeader } from '../../../app/details/cdDetails/utils'
56-
import { EnvironmentOverviewTable, EnvironmentOverviewTableProps } from '@Pages/Shared/EnvironmentOverviewTable'
56+
import { EnvironmentOverviewTable, EnvironmentOverviewTableRow } from '@Pages/Shared/EnvironmentOverviewTable'
5757
import './envOverview.scss'
5858

5959
const processDeploymentWindowAppGroupOverviewMap = importComponentFromFELibrary(
@@ -62,6 +62,8 @@ const processDeploymentWindowAppGroupOverviewMap = importComponentFromFELibrary(
6262
'function',
6363
)
6464
const ClonePipelineButton = importComponentFromFELibrary('ClonePipelineButton', null, 'function')
65+
const ClonePipelineMenuButton = importComponentFromFELibrary('ClonePipelineMenuButton', null, 'function')
66+
const ClonePipelineModal = importComponentFromFELibrary('ClonePipelineModal', null, 'function')
6567

6668
export default function EnvironmentOverview({
6769
appGroupListData,
@@ -82,8 +84,10 @@ export default function EnvironmentOverview({
8284
const [appStatusResponseList, setAppStatusResponseList] = useState<ManageAppsResponse[]>([])
8385
const timerId = useRef(null)
8486
const [selectedAppDetailsList, setSelectedAppDetailsList] = useState<AppInfoListType[]>([])
87+
const [selectedAppDetails, setSelectedAppDetails] = useState<AppInfoListType>(null)
8588
const [openedHibernateModalType, setOpenedHibernateModalType] =
8689
useState<HibernateModalProps['openedHibernateModalType']>(null)
90+
const [openClonePipelineConfig, setOpenClonePipelineConfig] = useState(false)
8791
const [commitInfoModalConfig, setCommitInfoModalConfig] = useState<Pick<
8892
ArtifactInfoModalProps,
8993
'ciArtifactId' | 'envId'
@@ -121,6 +125,8 @@ export default function EnvironmentOverview({
121125

122126
const { searchParams } = useSearchString()
123127
const history = useHistory()
128+
const isAppSelected = selectedAppDetails ?? !!selectedAppDetailsList.length
129+
const selectedApps = selectedAppDetails ?? selectedAppDetailsList
124130

125131
useEffect(() => {
126132
return () => {
@@ -131,7 +137,7 @@ export default function EnvironmentOverview({
131137
}, [])
132138

133139
async function getDeploymentWindowEnvOverrideMetaData() {
134-
const appEnvTuples = selectedAppDetailsList.map((appDetail) => ({
140+
const appEnvTuples = (selectedAppDetails ? [selectedAppDetails] : selectedAppDetailsList).map((appDetail) => ({
135141
appId: +appDetail.appId,
136142
envId: +appDetail.envId,
137143
}))
@@ -144,13 +150,14 @@ export default function EnvironmentOverview({
144150
useEffect(() => {
145151
if (
146152
processDeploymentWindowAppGroupOverviewMap &&
153+
isAppSelected &&
147154
(openedHibernateModalType ||
148155
showHibernateStatusDrawer.showStatus ||
149156
location.search.includes(URL_SEARCH_PARAMS.BULK_RESTART_WORKLOAD))
150157
) {
151158
getDeploymentWindowEnvOverrideMetaData()
152159
}
153-
}, [openedHibernateModalType, showHibernateStatusDrawer.showStatus, location.search])
160+
}, [openedHibernateModalType, showHibernateStatusDrawer.showStatus, location.search, isAppSelected])
154161

155162
useEffect(() => {
156163
setLoading(true)
@@ -247,10 +254,12 @@ export default function EnvironmentOverview({
247254
}
248255

249256
const openHibernateModalPopup = () => {
257+
setIsDeploymentLoading(true)
250258
setOpenedHibernateModalType(MODAL_TYPE.HIBERNATE)
251259
}
252260

253261
const openUnHibernateModalPopup = () => {
262+
setIsDeploymentLoading(true)
254263
setOpenedHibernateModalType(MODAL_TYPE.UNHIBERNATE)
255264
}
256265

@@ -282,25 +291,66 @@ export default function EnvironmentOverview({
282291
})
283292
}
284293

285-
const environmentOverviewTableRows: EnvironmentOverviewTableProps['rows'] = appListData?.appInfoList?.map(
286-
(appInfo) => ({
287-
environment: {
288-
id: appInfo.appId,
289-
name: appInfo.application,
290-
commits: appInfo.commits,
291-
deployedAt: appInfo.lastDeployed,
292-
status: appInfo.appStatus,
293-
deploymentStatus: appInfo.deploymentStatus,
294-
deployedBy: appInfo.lastDeployedBy,
295-
lastDeployedImage: appInfo.lastDeployedImage,
294+
const environmentOverviewTableRows: EnvironmentOverviewTableRow[] = appListData?.appInfoList?.map((appInfo) => ({
295+
environment: {
296+
id: appInfo.appId,
297+
name: appInfo.application,
298+
commits: appInfo.commits,
299+
deployedAt: appInfo.lastDeployed,
300+
status: appInfo.appStatus,
301+
deploymentStatus: appInfo.deploymentStatus,
302+
deployedBy: appInfo.lastDeployedBy,
303+
lastDeployedImage: appInfo.lastDeployedImage,
304+
},
305+
popUpMenuItems: [
306+
...((ClonePipelineMenuButton && appListData.environment
307+
? [
308+
<ClonePipelineMenuButton
309+
sourceEnvironmentName={appListData.environment}
310+
onClick={() => {
311+
setSelectedAppDetails(appInfo)
312+
setOpenClonePipelineConfig(true)
313+
}}
314+
/>,
315+
]
316+
: []) as EnvironmentOverviewTableRow['popUpMenuItems']),
317+
{
318+
label: 'Hibernate',
319+
Icon: HibernateIcon,
320+
iconType: null,
321+
disabled: !appInfo.lastDeployed,
322+
onClick: () => {
323+
setSelectedAppDetails(appInfo)
324+
openHibernateModalPopup()
325+
},
296326
},
297-
isChecked: selectedAppDetailsList.some(({ appId }) => appId === appInfo.appId),
298-
onLastDeployedImageClick: openCommitInfoModal(appInfo.ciArtifactId),
299-
onCommitClick: openCommitInfoModal(appInfo.ciArtifactId),
300-
deployedAtLink: getDeploymentHistoryLink(appInfo.appId, appInfo.pipelineId),
301-
redirectLink: getAppRedirectLink(appInfo.appId, +envId),
302-
}),
303-
)
327+
{
328+
label: 'Unhibernate',
329+
Icon: UnhibernateIcon,
330+
iconType: null,
331+
disabled: !appInfo.lastDeployed,
332+
onClick: () => {
333+
setSelectedAppDetails(appInfo)
334+
openUnHibernateModalPopup()
335+
},
336+
},
337+
{
338+
label: 'Restart Workload',
339+
Icon: RotateIcon,
340+
iconType: 'stroke',
341+
disabled: !appInfo.lastDeployed,
342+
onClick: () => {
343+
setSelectedAppDetails(appInfo)
344+
onClickShowBulkRestartModal()
345+
},
346+
},
347+
],
348+
isChecked: selectedAppDetailsList.some(({ appId }) => appId === appInfo.appId),
349+
onLastDeployedImageClick: openCommitInfoModal(appInfo.ciArtifactId),
350+
onCommitClick: openCommitInfoModal(appInfo.ciArtifactId),
351+
deployedAtLink: getDeploymentHistoryLink(appInfo.appId, appInfo.pipelineId),
352+
redirectLink: getAppRedirectLink(appInfo.appId, +envId),
353+
}))
304354

305355
const renderSideInfoColumn = () => {
306356
return (
@@ -378,10 +428,10 @@ export default function EnvironmentOverview({
378428
}
379429

380430
const renderOverviewModal = () => {
381-
if (location.search?.includes(URL_SEARCH_PARAMS.BULK_RESTART_WORKLOAD)) {
431+
if (isAppSelected && location.search?.includes(URL_SEARCH_PARAMS.BULK_RESTART_WORKLOAD)) {
382432
return (
383433
<RestartWorkloadModal
384-
selectedAppDetailsList={selectedAppDetailsList}
434+
selectedAppDetailsList={selectedApps}
385435
envName={appListData.environment}
386436
envId={envId}
387437
setRestartLoader={setRestartLoader}
@@ -393,10 +443,10 @@ export default function EnvironmentOverview({
393443
)
394444
}
395445

396-
if (openedHibernateModalType) {
446+
if (isAppSelected && openedHibernateModalType) {
397447
return (
398448
<HibernateModal
399-
selectedAppDetailsList={selectedAppDetailsList}
449+
selectedAppDetailsList={selectedApps}
400450
appDetailsList={appGroupListData.apps}
401451
envId={envId}
402452
envName={appListData.environment}
@@ -426,6 +476,17 @@ export default function EnvironmentOverview({
426476
)
427477
}
428478

479+
if (ClonePipelineModal && isAppSelected && openClonePipelineConfig) {
480+
return (
481+
<ClonePipelineModal
482+
sourceEnvironmentName={appListData.environment}
483+
selectedAppDetailsList={selectedApps}
484+
handleCloseClonePipelineModal={() => setOpenClonePipelineConfig(null)}
485+
httpProtocol={httpProtocol}
486+
/>
487+
)
488+
}
489+
429490
if (commitInfoModalConfig) {
430491
return (
431492
<ArtifactInfoModal
@@ -457,7 +518,6 @@ export default function EnvironmentOverview({
457518
httpProtocol={httpProtocol.current}
458519
/>
459520
)}
460-
461521
<button
462522
onClick={openHibernateModalPopup}
463523
className="bcn-0 fs-12 dc__border dc__border-radius-4-imp flex h-28"

0 commit comments

Comments
 (0)