Skip to content

Commit 604bc1a

Browse files
committed
feat: ChartStore Details - redesign preset values, code optimization
1 parent 710bd3b commit 604bc1a

File tree

9 files changed

+311
-28
lines changed

9 files changed

+311
-28
lines changed

src/Pages/ChartStore/ChartDetails/ChartDetails.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import {
55
APIResponseHandler,
66
BreadCrumb,
77
PageHeader,
8+
SearchBar,
89
SegmentedControl,
910
SegmentedControlProps,
1011
SelectPickerProps,
1112
useAsync,
1213
useBreadcrumb,
14+
useStateFilters,
1315
useUrlFilters,
1416
} from '@devtron-labs/devtron-fe-common-lib'
1517

@@ -19,10 +21,11 @@ import { getAvailableCharts } from '@Services/service'
1921

2022
import { ChartDetailsAbout } from './ChartDetailsAbout'
2123
import { ChartDetailsDeploy } from './ChartDetailsDeploy'
24+
import { ChartDetailsPresetValues } from './ChartDetailsPresetValues'
2225
import { ChartDetailsReadme } from './ChartDetailsReadme'
23-
import { CHART_DETAILS_SEGMENTS } from './constants'
26+
import { CHART_DETAILS_PORTAL_CONTAINER_ID, CHART_DETAILS_SEGMENTS } from './constants'
2427
import { fetchChartDetails, fetchChartVersions } from './services'
25-
import { ChartDetailsSearchParams, ChartDetailsSegment } from './types'
28+
import { ChartDetailsRouteParams, ChartDetailsSearchParams, ChartDetailsSegment } from './types'
2629
import { chartSelectorFilterOption, chartSelectorFormatOptionLabel, parseChartDetailsSearchParams } from './utils'
2730

2831
import './chartDetails.scss'
@@ -35,12 +38,14 @@ export const ChartDetails = () => {
3538
const {
3639
path,
3740
params: { chartId },
38-
} = useRouteMatch<{ chartId: string; chartSegment: ChartDetailsSegment }>()
41+
} = useRouteMatch<ChartDetailsRouteParams>()
3942

4043
const { tab, updateSearchParams } = useUrlFilters<void, ChartDetailsSearchParams>({
4144
parseSearchParams: parseChartDetailsSearchParams,
4245
})
4346

47+
const { searchKey, handleSearch, clearFilters } = useStateFilters()
48+
4449
// ASYNC CALLS
4550
const [isFetchingChartVersions, chartVersions, chartVersionsErr, reloadChartVersions] = useAsync(
4651
() => fetchChartVersions(chartId),
@@ -60,6 +65,10 @@ export const ChartDetails = () => {
6065
}
6166
}, [isFetchingChartVersions, chartVersions])
6267

68+
useEffect(() => {
69+
clearFilters()
70+
}, [tab])
71+
6372
// CONFIGS
6473
const { breadcrumbs } = useBreadcrumb(
6574
{
@@ -123,7 +132,7 @@ export const ChartDetails = () => {
123132
/>
124133
)
125134
case ChartDetailsSegment.PRESET_VALUES:
126-
return <div>PRESET VALUES</div>
135+
return <ChartDetailsPresetValues searchKey={searchKey} onClearFilters={clearFilters} />
127136
case ChartDetailsSegment.DEPLOYMENTS:
128137
return <div>DEPLOYMENTS</div>
129138
default:
@@ -151,13 +160,26 @@ export const ChartDetails = () => {
151160
</Route>
152161
<Route>
153162
<div className="chart-details flex-grow-1 p-20 dc__overflow-auto">
154-
<div className="flex column left top dc__gap-16 mw-none">
155-
<SegmentedControl
156-
name="chart-details-segmented-control"
157-
segments={CHART_DETAILS_SEGMENTS}
158-
value={tab}
159-
onChange={handleSegmentChange}
160-
/>
163+
<div className="flexbox-col dc__gap-16 mw-none">
164+
<div className="flex dc__content-space">
165+
<SegmentedControl
166+
name="chart-details-segmented-control"
167+
segments={CHART_DETAILS_SEGMENTS}
168+
value={tab}
169+
onChange={handleSegmentChange}
170+
/>
171+
{(tab === ChartDetailsSegment.PRESET_VALUES ||
172+
tab === ChartDetailsSegment.DEPLOYMENTS) && (
173+
<div id={CHART_DETAILS_PORTAL_CONTAINER_ID} className="flex dc__gap-8">
174+
<SearchBar
175+
containerClassName="w-250"
176+
dataTestId="chart-details-search-bar"
177+
initialSearchText={searchKey}
178+
handleEnter={handleSearch}
179+
/>
180+
</div>
181+
)}
182+
</div>
161183
{renderSegments()}
162184
</div>
163185
<ChartDetailsAbout isLoading={isFetchingChartDetails} chartDetails={chartDetails} />

src/Pages/ChartStore/ChartDetails/ChartDetailsAbout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export const ChartDetailsAbout = ({ chartDetails, isLoading }: ChartDetailsAbout
146146
/>
147147
)}
148148
<div className="flexbox-col dc__gap-12">
149-
<ChartMetaData title="Chart repository" subtitle={chartName} />
149+
<ChartMetaData title="Chart source" subtitle={chartName} />
150150
<ChartMetaData title="Home" subtitle={home} isLink />
151151
<ChartMetaData
152152
title="Application Version"

src/Pages/ChartStore/ChartDetails/ChartDetailsDeploy.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import ChartValuesView from '@Components/v2/values/chartValuesDiff/ChartValuesVi
77
import { ChartInstalledConfig } from '@Components/v2/values/chartValuesDiff/ChartValuesView.type'
88

99
import { fetchChartValuesList } from './services'
10-
import { ChartDetailsDeployProps } from './types'
10+
import { ChartDetailsDeployProps, ChartDetailsRouteParams } from './types'
1111

1212
export const ChartDetailsDeploy = ({ chartDetails, chartVersions, selectedChartVersion }: ChartDetailsDeployProps) => {
13-
const { chartId } = useParams<{ chartId: string }>()
13+
const { chartId } = useParams<ChartDetailsRouteParams>()
1414

1515
const [isFetchingChartValuesList, chartValuesList, chartValuesListErr, reloadChartValuesList] = useAsync(
1616
() => fetchChartValuesList(chartId),
@@ -31,7 +31,7 @@ export const ChartDetailsDeploy = ({ chartDetails, chartVersions, selectedChartV
3131
return (
3232
<APIResponseHandler
3333
isLoading={isChartValuesViewLoading}
34-
progressingProps={{ pageLoader: true }}
34+
progressingProps={{ size: 24 }}
3535
error={chartValuesListErr}
3636
errorScreenManagerProps={{ code: chartValuesListErr?.code, reload: reloadChartValuesList }}
3737
>
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { useMemo, useState } from 'react'
2+
import { generatePath, Link, useRouteMatch } from 'react-router-dom'
3+
4+
import {
5+
APIResponseHandler,
6+
Button,
7+
ButtonComponentType,
8+
ButtonStyleType,
9+
ButtonVariantType,
10+
ComponentSizeType,
11+
DeleteConfirmationModal,
12+
GenericEmptyState,
13+
handleAnalyticsEvent,
14+
Icon,
15+
PortalContainer,
16+
SortableTableHeaderCell,
17+
stringComparatorBySortOrder,
18+
useAsync,
19+
useStateFilters,
20+
} from '@devtron-labs/devtron-fe-common-lib'
21+
22+
import { deleteChartValues } from '@Components/charts/charts.service'
23+
import { SavedValueType } from '@Components/charts/SavedValues/types'
24+
import { URLS } from '@Config/routes'
25+
import { ApplicationDeletionInfo } from '@Pages/Shared/ApplicationDeletionInfo/ApplicationDeletionInfo'
26+
27+
import { CHART_DETAILS_PORTAL_CONTAINER_ID } from './constants'
28+
import { fetchChartValuesTemplateList } from './services'
29+
import { ChartDetailsPresetValuesProps, ChartDetailsRouteParams } from './types'
30+
31+
const renderEmptyStateButton = (path: string) => () => (
32+
<Button
33+
dataTestId="create-chart-preset-value"
34+
variant={ButtonVariantType.secondary}
35+
text="Create Preset Value"
36+
startIcon={<Icon name="ic-add" color={null} />}
37+
size={ComponentSizeType.medium}
38+
component={ButtonComponentType.link}
39+
linkProps={{ to: `${path}${URLS.PRESET_VALUES}/0` }}
40+
/>
41+
)
42+
43+
const renderFilterEmptyStateButton = (onClick: () => void) => () => (
44+
<Button
45+
dataTestId="chart-preset-values-clear-filters"
46+
variant={ButtonVariantType.secondary}
47+
text="Clear Filters"
48+
size={ComponentSizeType.medium}
49+
onClick={onClick}
50+
/>
51+
)
52+
53+
export const ChartDetailsPresetValues = ({ searchKey, onClearFilters }: ChartDetailsPresetValuesProps) => {
54+
// STATES
55+
const [deletePresetValue, setDeletePresetValue] = useState<SavedValueType | null>(null)
56+
57+
// HOOKS
58+
const {
59+
path,
60+
params: { chartId },
61+
} = useRouteMatch<ChartDetailsRouteParams>()
62+
63+
// ASYNC CALLS
64+
const [
65+
isFetchingChartValuesTemplateList,
66+
chartValuesTemplateList,
67+
chartValuesTemplateListErr,
68+
reloadChartValuesTemplateList,
69+
] = useAsync(() => fetchChartValuesTemplateList(chartId), [chartId])
70+
71+
const { sortBy, sortOrder, handleSorting } = useStateFilters<'name'>({ initialSortKey: 'name' })
72+
73+
const filteredChartValuesTemplateList = useMemo(() => {
74+
if (!isFetchingChartValuesTemplateList && chartValuesTemplateList) {
75+
return chartValuesTemplateList
76+
.filter((cluster) => cluster.name.includes(searchKey.toLowerCase()))
77+
.sort((a, b) => stringComparatorBySortOrder(a.name, b.name, sortOrder))
78+
}
79+
80+
return []
81+
}, [chartValuesTemplateList, isFetchingChartValuesTemplateList, searchKey, sortOrder])
82+
83+
// HANDLERS
84+
const triggerSorting = () => {
85+
handleSorting('name')
86+
}
87+
88+
const handleChartPresetDeployAndEdit = () => {
89+
handleAnalyticsEvent({ category: 'Chart Store', action: 'CS_CHART_PRESET_VALUES_NEW' })
90+
}
91+
92+
const handleChartPresetDelete = async () => {
93+
await deleteChartValues(deletePresetValue.id)
94+
reloadChartValuesTemplateList()
95+
}
96+
97+
const showDeleteModal = (_deletePresetValue: typeof deletePresetValue) => () => {
98+
setDeletePresetValue(_deletePresetValue)
99+
}
100+
101+
const hideDeleteModal = () => {
102+
setDeletePresetValue(null)
103+
}
104+
105+
return (
106+
<div className="flex-grow-1 flexbox-col bg__primary border__primary br-4 w-100 dc__overflow-hidden">
107+
<PortalContainer
108+
portalParentId={CHART_DETAILS_PORTAL_CONTAINER_ID}
109+
condition={!isFetchingChartValuesTemplateList && !!chartValuesTemplateList?.length}
110+
>
111+
<Button
112+
dataTestId="chart-preset-values-clear-filters"
113+
variant={ButtonVariantType.secondary}
114+
startIcon={<Icon name="ic-add" color={null} />}
115+
text="Create Preset"
116+
size={ComponentSizeType.medium}
117+
component={ButtonComponentType.link}
118+
linkProps={{ to: `${generatePath(path, { chartId })}${URLS.PRESET_VALUES}/0` }}
119+
/>
120+
</PortalContainer>
121+
<APIResponseHandler
122+
isLoading={isFetchingChartValuesTemplateList}
123+
progressingProps={{ size: 24 }}
124+
error={chartValuesTemplateListErr}
125+
errorScreenManagerProps={{
126+
code: chartValuesTemplateListErr?.code,
127+
reload: reloadChartValuesTemplateList,
128+
}}
129+
>
130+
{!chartValuesTemplateList?.length && (
131+
<GenericEmptyState
132+
title="Create your first Preset Template"
133+
subTitle="Create reusable Helm config templates for different scenarios. Set them up once and let your team deploy with confidence."
134+
isButtonAvailable
135+
renderButton={renderEmptyStateButton(generatePath(path, { chartId }))}
136+
/>
137+
)}
138+
{!!chartValuesTemplateList?.length &&
139+
(filteredChartValuesTemplateList.length ? (
140+
<>
141+
<div className="chart-details-preset-value__row px-16 pt-6 pb-5 border__primary--bottom">
142+
<span className="icon-dim-24" />
143+
<SortableTableHeaderCell
144+
title="Name"
145+
isSortable
146+
isSorted={sortBy === 'name'}
147+
sortOrder={sortOrder}
148+
triggerSorting={triggerSorting}
149+
disabled={false}
150+
/>
151+
<SortableTableHeaderCell title="Version" isSortable={false} />
152+
</div>
153+
{filteredChartValuesTemplateList.map(({ chartVersion, id, name, ...rest }) => (
154+
<div
155+
key={id}
156+
className="chart-details-preset-value__row px-16 py-12 bg__hover dc__visible-hover dc__visible-hover--parent"
157+
>
158+
<Icon name="ic-file" color="N700" size={24} />
159+
<Link
160+
className="fs-13 lh-20"
161+
to={`${generatePath(path, { chartId })}${URLS.PRESET_VALUES}/${id}`}
162+
>
163+
{name}
164+
</Link>
165+
<div className="flex dc__content-space">
166+
<span className="fs-13 lh-20 cn-9">{chartVersion}</span>
167+
<div className="flex dc__gap-4 dc__visible-hover--child">
168+
<Button
169+
dataTestId="chart-deploy-with-preset-value"
170+
ariaLabel="chart-deploy-with-preset-value"
171+
showAriaLabelInTippy={false}
172+
icon={<Icon name="ic-rocket-launch" color={null} />}
173+
variant={ButtonVariantType.borderLess}
174+
style={ButtonStyleType.neutral}
175+
size={ComponentSizeType.xs}
176+
component={ButtonComponentType.link}
177+
linkProps={{
178+
to: `${generatePath(path, { chartId })}${URLS.DEPLOY_CHART}/${id}`,
179+
}}
180+
onClick={handleChartPresetDeployAndEdit}
181+
/>
182+
<Button
183+
dataTestId="chart-preset-value-edit"
184+
ariaLabel="chart-preset-value-edit"
185+
showAriaLabelInTippy={false}
186+
icon={<Icon name="ic-edit" color={null} />}
187+
variant={ButtonVariantType.borderLess}
188+
style={ButtonStyleType.neutral}
189+
size={ComponentSizeType.xs}
190+
component={ButtonComponentType.link}
191+
linkProps={{
192+
to: `${generatePath(path, { chartId })}${URLS.PRESET_VALUES}/${id}`,
193+
}}
194+
onClick={handleChartPresetDeployAndEdit}
195+
/>
196+
<Button
197+
dataTestId="chart-preset-value-delete"
198+
ariaLabel="chart-preset-value-delete"
199+
showAriaLabelInTippy={false}
200+
icon={<Icon name="ic-delete" color={null} />}
201+
variant={ButtonVariantType.borderLess}
202+
style={ButtonStyleType.neutral}
203+
size={ComponentSizeType.xs}
204+
onClick={showDeleteModal({ chartVersion, id, name, ...rest })}
205+
/>
206+
</div>
207+
</div>
208+
</div>
209+
))}
210+
</>
211+
) : (
212+
<GenericEmptyState
213+
title="No results"
214+
subTitle="We couldn’t find any matching results"
215+
isButtonAvailable
216+
renderButton={renderFilterEmptyStateButton(onClearFilters)}
217+
/>
218+
))}
219+
</APIResponseHandler>
220+
{deletePresetValue && (
221+
<DeleteConfirmationModal
222+
title={deletePresetValue.name}
223+
subtitle={<ApplicationDeletionInfo isPresetValue />}
224+
component="preset value"
225+
onDelete={handleChartPresetDelete}
226+
closeConfirmationModal={hideDeleteModal}
227+
/>
228+
)}
229+
</div>
230+
)
231+
}

src/Pages/ChartStore/ChartDetails/chartDetails.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@
99
width: 56px;
1010
height: 56px;
1111
}
12+
13+
.chart-details-preset-value {
14+
&__row {
15+
display: grid;
16+
align-items: center;
17+
grid-template-columns: 24px 200px 1fr;
18+
gap: 16px;
19+
}
20+
}

src/Pages/ChartStore/ChartDetails/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ export const CHART_DETAILS_SEGMENTS: SegmentedControlProps['segments'] = [
1616
value: ChartDetailsSegment.DEPLOYMENTS,
1717
},
1818
]
19+
20+
export const CHART_DETAILS_PORTAL_CONTAINER_ID = 'chart-details-portal-container'

0 commit comments

Comments
 (0)