Skip to content

Commit b9723d0

Browse files
committed
feat: ChartDetails - Deployments - Revamp design, code optimizations
1 parent 2182f62 commit b9723d0

10 files changed

+557
-91
lines changed

src/Pages/ChartStore/ChartDetails/ChartDetails.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Route, Switch, useRouteMatch } from 'react-router-dom'
44
import {
55
APIResponseHandler,
66
BreadCrumb,
7+
handleAnalyticsEvent,
78
PageHeader,
89
SegmentedControl,
910
SegmentedControlProps,
@@ -102,7 +103,13 @@ export const ChartDetails = () => {
102103

103104
// HANDLERS
104105
const handleSegmentChange: SegmentedControlProps['onChange'] = (selectedSegment) => {
105-
updateSearchParams({ tab: selectedSegment.value as ChartDetailsSegment })
106+
const updatedTab = selectedSegment.value as ChartDetailsSegment
107+
108+
if (updatedTab === ChartDetailsSegment.PRESET_VALUES) {
109+
handleAnalyticsEvent({ category: 'Chart Store', action: 'CS_CHART_PRESET_VALUES' })
110+
}
111+
112+
updateSearchParams({ tab: updatedTab })
106113
}
107114

108115
const handleChartChange: SelectPickerProps<number>['onChange'] = ({ value }) => {

src/Pages/ChartStore/ChartDetails/ChartDetailsAbout.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
ComponentSizeType,
99
handleAnalyticsEvent,
1010
Icon,
11+
ImageWithFallback,
1112
InfoBlock,
1213
} from '@devtron-labs/devtron-fe-common-lib'
1314

@@ -16,7 +17,7 @@ import { getParsedChartYAML } from './utils'
1617

1718
const AboutHeaderLoadingSkeleton = () => (
1819
<div className="flexbox-col dc__gap-16">
19-
<div className="shimmer-loading loading-icon" />
20+
<div className="shimmer-loading chart-details__loading-icon" />
2021
<div className="shimmer-loading w-150 h-20" />
2122
<div className="flexbox-col dc__gap-8">
2223
<div className="shimmer-loading w-250 h-16" />
@@ -110,13 +111,15 @@ export const ChartDetailsAbout = ({ chartDetails, isLoading }: ChartDetailsAbout
110111
return (
111112
<div className="flexbox-col dc__gap-20 mw-none">
112113
<div className="flexbox-col dc__gap-12">
113-
{icon ? (
114-
<div className="icon-dim-48">
115-
<img src={icon} className="w-100" alt="chart-icon" data-testid="chart-type-image" />
116-
</div>
117-
) : (
118-
<Icon name="ic-helm" color="N700" size={48} />
119-
)}
114+
<ImageWithFallback
115+
imageProps={{
116+
src: icon,
117+
alt: 'chart-icon',
118+
height: 48,
119+
width: 48,
120+
}}
121+
fallbackImage={<Icon name="ic-helm" color="N700" size={48} />}
122+
/>
120123
<h2 className="m-0 fs-16 lh-24 fw-6 cn-9">{name}</h2>
121124
<p className="m-0 fs-13 lh-20 cn-9">{description}</p>
122125
</div>
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { useMemo, useState } from 'react'
2+
import { useParams } from 'react-router-dom'
3+
4+
import {
5+
APIResponseHandler,
6+
ConfirmationModal,
7+
ConfirmationModalProps,
8+
ConfirmationModalVariantType,
9+
DeploymentAppTypes,
10+
ERROR_STATUS_CODE,
11+
FiltersTypeEnum,
12+
ForceDeleteConfirmationModal,
13+
PaginationEnum,
14+
ServerErrors,
15+
showError,
16+
Table,
17+
ToastManager,
18+
ToastVariantType,
19+
useAsync,
20+
} from '@devtron-labs/devtron-fe-common-lib'
21+
22+
import ClusterNotReachableDialog from '@Components/common/ClusterNotReachableDialog/ClusterNotReachableDialog'
23+
import { DELETE_ACTION } from '@Config/constants'
24+
import { ApplicationDeletionInfo } from '@Pages/Shared/ApplicationDeletionInfo/ApplicationDeletionInfo'
25+
26+
import { DEPLOYMENTS_TABLE_COLUMNS, DeploymentsTableViewWrapper } from './ChartDetailsTableComponents'
27+
import { deleteChartDeployment, fetchChartDeployments } from './services'
28+
import {
29+
ChartDeploymentsDTO,
30+
ChartDetailsDeploymentsProps,
31+
ChartDetailsRouteParams,
32+
DeploymentsTableAdditionalProps,
33+
DeploymentsTableProps,
34+
} from './types'
35+
36+
export const ChartDetailsDeployments = ({ chartIcon }: ChartDetailsDeploymentsProps) => {
37+
// STATES
38+
const [isOpenDeleteConfirmationModal, setIsOpenDeleteConfirmationModal] = useState(false)
39+
const [deleteAppDetails, setDeleteAppDetails] = useState<ChartDeploymentsDTO | null>(null)
40+
const [isDeleting, setIsDeleting] = useState(false)
41+
const [forceDeleteDialogDetails, setForceDeleteDialogDetails] = useState<Pick<
42+
ConfirmationModalProps,
43+
'title' | 'subtitle'
44+
> | null>(null)
45+
const [nonCascadeDeleteDialogClusterName, setNonCascadeDeleteDialogClusterName] = useState<string | null>(null)
46+
47+
// HOOKS
48+
const { chartId } = useParams<ChartDetailsRouteParams>()
49+
50+
// ASYNC CALLS
51+
const [isFetchingChartDeployments, chartDeployments, chartDeploymentsErr, reloadChartDeployments] = useAsync(
52+
() => fetchChartDeployments(chartId),
53+
[chartId],
54+
)
55+
56+
// CONFIGS
57+
const rows = useMemo<DeploymentsTableProps['rows']>(
58+
() =>
59+
(chartDeployments || []).map<DeploymentsTableProps['rows'][0]>((data) => ({
60+
id: data.installedAppId.toString(),
61+
data,
62+
})),
63+
[chartDeployments],
64+
)
65+
66+
// HANDLERS
67+
const filter: DeploymentsTableProps['filter'] = (rowData, filterData) =>
68+
rowData.data.appName.includes(filterData.searchKey.toLowerCase())
69+
70+
const handleCloseDeleteConfirmationModal = () => setIsOpenDeleteConfirmationModal(false)
71+
72+
const handleCloseForceDeleteModal = () => {
73+
setIsOpenDeleteConfirmationModal(false)
74+
setForceDeleteDialogDetails(null)
75+
}
76+
77+
const handleCloseNonCascadeDeleteModal = () => {
78+
setNonCascadeDeleteDialogClusterName(null)
79+
}
80+
81+
const handleRowDelete = (rowData: ChartDeploymentsDTO) => {
82+
setIsOpenDeleteConfirmationModal(true)
83+
setDeleteAppDetails(rowData)
84+
}
85+
86+
const handleDelete = async (deleteAction: DELETE_ACTION) => {
87+
setIsDeleting(true)
88+
89+
try {
90+
const result = await deleteChartDeployment({
91+
installedAppId: deleteAppDetails.installedAppId,
92+
isGitops: deleteAppDetails.deploymentAppType === DeploymentAppTypes.ARGO,
93+
deleteAction,
94+
})
95+
if (result.deleteResponse?.deleteInitiated) {
96+
ToastManager.showToast({
97+
variant: ToastVariantType.success,
98+
description: 'Successfully deleted',
99+
})
100+
setIsOpenDeleteConfirmationModal(false)
101+
setNonCascadeDeleteDialogClusterName(null)
102+
setForceDeleteDialogDetails(null)
103+
reloadChartDeployments()
104+
} else if (
105+
deleteAction !== DELETE_ACTION.NONCASCADE_DELETE &&
106+
!result.deleteResponse?.clusterReachable &&
107+
deleteAppDetails.deploymentAppType === DeploymentAppTypes.ARGO
108+
) {
109+
setNonCascadeDeleteDialogClusterName(result.deleteResponse?.clusterName)
110+
setIsOpenDeleteConfirmationModal(false)
111+
}
112+
} catch (err) {
113+
if (deleteAction !== DELETE_ACTION.FORCE_DELETE && err.code !== ERROR_STATUS_CODE.PERMISSION_DENIED) {
114+
setIsOpenDeleteConfirmationModal(false)
115+
setNonCascadeDeleteDialogClusterName(null)
116+
if (err instanceof ServerErrors && Array.isArray(err.errors)) {
117+
err.errors.forEach(({ userMessage, internalMessage }) => {
118+
setForceDeleteDialogDetails({
119+
title: userMessage,
120+
subtitle: internalMessage,
121+
})
122+
})
123+
}
124+
} else {
125+
showError(err)
126+
}
127+
} finally {
128+
setIsDeleting(false)
129+
}
130+
}
131+
132+
const handleCascadeDelete = async () => {
133+
await handleDelete(DELETE_ACTION.DELETE)
134+
}
135+
136+
const handleForceDelete = async () => {
137+
await handleDelete(DELETE_ACTION.FORCE_DELETE)
138+
}
139+
140+
const handleNonCascadeDelete = async () => {
141+
await handleDelete(DELETE_ACTION.NONCASCADE_DELETE)
142+
}
143+
144+
return (
145+
<>
146+
<div className="mh-500 flexbox-col bg__primary border__primary br-4 w-100 dc__overflow-auto">
147+
<APIResponseHandler
148+
isLoading={false}
149+
progressingProps={{ size: 24 }}
150+
error={chartDeploymentsErr}
151+
errorScreenManagerProps={{
152+
code: chartDeploymentsErr?.code,
153+
reload: reloadChartDeployments,
154+
}}
155+
>
156+
<Table<ChartDeploymentsDTO, FiltersTypeEnum.STATE, DeploymentsTableAdditionalProps>
157+
id="table__chart-details-deployments"
158+
loading={isFetchingChartDeployments}
159+
columns={DEPLOYMENTS_TABLE_COLUMNS}
160+
rows={rows}
161+
stylesConfig={{ showSeparatorBetweenRows: false }}
162+
emptyStateConfig={{
163+
noRowsConfig: {
164+
title: 'This chart is ready for launch',
165+
subTitle:
166+
'This chart hasn’t been deployed yet. Click Deploy Chart to get started with your first deployment.',
167+
imgName: 'img-man-on-rocket',
168+
},
169+
noRowsForFilterConfig: {
170+
title: 'No results',
171+
subTitle: 'We couldn’t find any matching results',
172+
},
173+
}}
174+
paginationVariant={PaginationEnum.NOT_PAGINATED}
175+
filtersVariant={FiltersTypeEnum.STATE}
176+
filter={filter}
177+
ViewWrapper={DeploymentsTableViewWrapper}
178+
additionalProps={{
179+
chartIcon,
180+
onDelete: handleRowDelete,
181+
}}
182+
additionalFilterProps={{
183+
initialSortKey: 'appName',
184+
}}
185+
/>
186+
</APIResponseHandler>
187+
</div>
188+
{isOpenDeleteConfirmationModal && (
189+
<ConfirmationModal
190+
variant={ConfirmationModalVariantType.delete}
191+
title={`Delete app ‘${deleteAppDetails.appName}’`}
192+
subtitle={<ApplicationDeletionInfo />}
193+
buttonConfig={{
194+
secondaryButtonConfig: {
195+
onClick: handleCloseDeleteConfirmationModal,
196+
text: 'Cancel',
197+
},
198+
primaryButtonConfig: {
199+
isLoading: isDeleting,
200+
onClick: handleCascadeDelete,
201+
text: 'Delete',
202+
},
203+
}}
204+
handleClose={handleCloseDeleteConfirmationModal}
205+
/>
206+
)}
207+
{!!forceDeleteDialogDetails && (
208+
<ForceDeleteConfirmationModal
209+
{...forceDeleteDialogDetails}
210+
onDelete={handleForceDelete}
211+
closeConfirmationModal={handleCloseForceDeleteModal}
212+
/>
213+
)}
214+
{!!nonCascadeDeleteDialogClusterName && (
215+
<ClusterNotReachableDialog
216+
clusterName={nonCascadeDeleteDialogClusterName}
217+
onClickCancel={handleCloseNonCascadeDeleteModal}
218+
onClickDelete={handleNonCascadeDelete}
219+
/>
220+
)}
221+
</>
222+
)
223+
}

src/Pages/ChartStore/ChartDetails/ChartDetailsPresetValues.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@ import { URLS } from '@Config/routes'
2121
import { ApplicationDeletionInfo } from '@Pages/Shared/ApplicationDeletionInfo/ApplicationDeletionInfo'
2222

2323
import {
24+
PRESET_VALUES_TABLE_COLUMNS,
2425
PresetValuesTableRowActionsOnHoverComponent,
2526
PresetValuesTableViewWrapper,
2627
} from './ChartDetailsTableComponents'
27-
import { PRESET_VALUES_TABLE_COLUMNS } from './constants'
2828
import { fetchChartValuesTemplateList } from './services'
29-
import { ChartDetailsRouteParams, PresetValuesTable } from './types'
29+
import {
30+
ChartDetailsRouteParams,
31+
PresetValuesTableAdditionalProps,
32+
PresetValuesTableProps,
33+
PresetValuesTableRowData,
34+
} from './types'
3035

3136
const renderEmptyStateButton = (path: string) => () => (
3237
<Button
@@ -58,9 +63,9 @@ export const ChartDetailsPresetValues = () => {
5863
reloadChartValuesTemplateList,
5964
] = useAsync(() => fetchChartValuesTemplateList(chartId), [chartId], true, { resetOnChange: false })
6065

61-
const rows = useMemo<PresetValuesTable['rows']>(
66+
const rows = useMemo<PresetValuesTableProps['rows']>(
6267
() =>
63-
(chartValuesTemplateList || []).map<PresetValuesTable['rows'][0]>(
68+
(chartValuesTemplateList || []).map<PresetValuesTableProps['rows'][0]>(
6469
({ id, chartVersion, name, updatedBy, updatedOn }) => ({
6570
id: id.toString(),
6671
data: { chartVersion, name, updatedBy, updatedOn, id },
@@ -83,7 +88,7 @@ export const ChartDetailsPresetValues = () => {
8388
setDeletePresetValue(null)
8489
}
8590

86-
const filter: PresetValuesTable['filter'] = (rowData, filterData) =>
91+
const filter: PresetValuesTableProps['filter'] = (rowData, filterData) =>
8792
rowData.data.name.includes(filterData.searchKey.toLowerCase())
8893

8994
return (
@@ -97,7 +102,7 @@ export const ChartDetailsPresetValues = () => {
97102
reload: reloadChartValuesTemplateList,
98103
}}
99104
>
100-
<Table
105+
<Table<PresetValuesTableRowData, FiltersTypeEnum.STATE, PresetValuesTableAdditionalProps>
101106
id="table__chart-details-preset-values"
102107
loading={isFetchingChartValuesTemplateList}
103108
columns={PRESET_VALUES_TABLE_COLUMNS}

src/Pages/ChartStore/ChartDetails/ChartDetailsReadme.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const ChartDetailsReadme = ({
6767
<GenericEmptyState
6868
title={`Readme not available for ${chartName} ${selectedChartVersionValue?.label}`}
6969
subTitle="A readme file was not found for this chart’s version."
70-
illustrationName="img-no-result"
70+
imgName="img-no-result"
7171
isButtonAvailable
7272
renderButton={renderEmptyStateButton}
7373
/>

0 commit comments

Comments
 (0)