Skip to content

Commit e523974

Browse files
committed
feat: implement ChartsList component and integrate with chart provider list
1 parent 4701465 commit e523974

File tree

6 files changed

+261
-108
lines changed

6 files changed

+261
-108
lines changed

src/components/chartRepo/chartRepo.service.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { post, get, trash } from '@devtron-labs/devtron-fe-common-lib'
17+
import { post, get, trash, APIOptions } from '@devtron-labs/devtron-fe-common-lib'
1818
import { Routes } from '../../config'
1919

2020
export const getChartProviderConfig = (id: number): Promise<any> => {
@@ -37,9 +37,9 @@ export const validateChartRepoConfiguration = (request: any): Promise<any> => {
3737
return post(URL, request)
3838
}
3939

40-
export const reSyncChartRepo = (): Promise<any> => {
40+
export const reSyncChartRepo = ({ signal }: Pick<APIOptions, 'signal'> = {}) => {
4141
const URL = `${Routes.CHART_REPO}/${Routes.CHART_RESYNC}`
42-
return post(URL, undefined)
42+
return post(URL, undefined, { signal })
4343
}
4444

4545
export function deleteChartRepo(request) {

src/components/charts/charts.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
HelmProjectUpdatePayload,
3434
DeleteInstalledChartParamsType,
3535
ChartDetailsDTO,
36+
ChartListType,
3637
} from './charts.types'
3738

3839
interface RootObject {
@@ -288,8 +289,8 @@ export function updateHelmAppProject(payload: HelmProjectUpdatePayload): Promise
288289
return put(Routes.UPDATE_HELM_APP_META_INFO, payload)
289290
}
290291

291-
export function getChartProviderList(): Promise<ResponseType> {
292-
return get('app-store/chart-provider/list')
292+
export function getChartProviderList() {
293+
return get<ChartListType[]>('app-store/chart-provider/list')
293294
}
294295

295296
export function updateChartProviderList(payload) {
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import { useEffect, useMemo, useState } from 'react'
2+
import { NavLink } from 'react-router-dom'
3+
4+
import {
5+
Button,
6+
ButtonStyleType,
7+
ButtonVariantType,
8+
ComponentSizeType,
9+
EMPTY_STATE_STATUS,
10+
GenericEmptyState,
11+
handleAnalyticsEvent,
12+
Icon,
13+
ImageType,
14+
InfoBlock,
15+
Popover,
16+
Progressing,
17+
SearchBar,
18+
ToastManager,
19+
ToastVariantType,
20+
usePopover,
21+
useQuery,
22+
} from '@devtron-labs/devtron-fe-common-lib'
23+
24+
import { reSyncChartRepo } from '@Components/chartRepo/chartRepo.service'
25+
import { TOAST_INFO } from '@Config/constantMessaging'
26+
import { URLS } from '@Config/routes'
27+
28+
import AddChartSource from './AddChartSource'
29+
import ChartListPopUpRow from './ChartListPopUpRow'
30+
import { ChartsListProps } from './types'
31+
32+
export const ChartsList = ({ chartsList, isLoading }: ChartsListProps) => {
33+
// STATES
34+
const [searchText, setSearchText] = useState('')
35+
const [chartActiveMap, setChartActiveMap] = useState<Record<string, boolean>>({})
36+
37+
// HOOKS
38+
const { open, overlayProps, popoverProps, scrollableRef, triggerProps, closePopover } = usePopover({
39+
id: 'chart-list-popover',
40+
alignment: 'middle',
41+
width: 400,
42+
})
43+
44+
// QUERIES
45+
const { isFetching, isSuccess, refetch } = useQuery({
46+
queryKey: ['reSyncChartRepo'],
47+
queryFn: reSyncChartRepo,
48+
enabled: false,
49+
})
50+
51+
useEffect(() => {
52+
setChartActiveMap(
53+
(chartsList ?? []).reduce((acc, curr) => {
54+
acc[curr.name] = curr.active
55+
return acc
56+
}, {}),
57+
)
58+
}, [chartsList])
59+
60+
useEffect(() => {
61+
if (!open) {
62+
setSearchText('')
63+
}
64+
}, [open])
65+
66+
useEffect(() => {
67+
if (!isFetching && isSuccess) {
68+
ToastManager.showToast({
69+
variant: ToastVariantType.success,
70+
description: TOAST_INFO.RE_SYNC,
71+
})
72+
}
73+
}, [isFetching, isSuccess])
74+
75+
// COMPUTED VALUES
76+
const filteredChartList = useMemo(
77+
() => (chartsList ?? []).filter((chart) => chart.name.toLowerCase().indexOf(searchText.toLowerCase()) >= 0),
78+
[chartsList, searchText],
79+
)
80+
81+
// HANDLERS
82+
const handleSourceBtnClick = () => {
83+
handleAnalyticsEvent({ category: 'Chart Store', action: 'CS_SOURCE' })
84+
}
85+
86+
const toggleEnabled = (key: string) => (enabled: boolean) =>
87+
setChartActiveMap({ ...chartActiveMap, [key]: enabled })
88+
89+
const handleSearchEnter = (searchKey: string) => {
90+
setSearchText(searchKey)
91+
}
92+
93+
const handleRefetchCharts = async () => {
94+
await refetch()
95+
}
96+
97+
// RENDERERS
98+
const renderChartListBody = () => {
99+
if (isLoading) {
100+
return (
101+
<div ref={scrollableRef} className="flex column dc__gap-12 flex-grow-1">
102+
<Progressing size={24} />
103+
<span className="dc__loading-dots">Loading Chart source</span>
104+
</div>
105+
)
106+
}
107+
108+
if (!chartsList.length) {
109+
return (
110+
<div ref={scrollableRef} className="flex-grow-1 flex">
111+
<GenericEmptyState
112+
imgName="img-no-result"
113+
title={EMPTY_STATE_STATUS.CHART.NO_SOURCE_TITLE}
114+
subTitle={
115+
<div>
116+
<span>Add a &nbsp;</span>
117+
<NavLink to={URLS.GLOBAL_CONFIG_CHART}>Chart repositories</NavLink>
118+
<span>&nbsp;or&nbsp;</span>
119+
<NavLink to={URLS.GLOBAL_CONFIG_DOCKER}>OCI Registries</NavLink>
120+
<span>to view and deploy helm charts.</span>
121+
</div>
122+
}
123+
imageType={ImageType.Medium}
124+
/>
125+
</div>
126+
)
127+
}
128+
129+
return (
130+
<>
131+
<SearchBar
132+
dataTestId="chart-store-search-box"
133+
initialSearchText={searchText}
134+
containerClassName="p-12"
135+
handleEnter={handleSearchEnter}
136+
inputProps={{
137+
placeholder: 'Search by repository or registry',
138+
autoFocus: true,
139+
}}
140+
/>
141+
142+
{!!searchText && !filteredChartList.length ? (
143+
<div ref={scrollableRef} className="flex-grow-1 flex">
144+
<GenericEmptyState
145+
imgName="img-no-result"
146+
title={`No result for "${searchText}"`}
147+
subTitle={EMPTY_STATE_STATUS.CHART.NO_CHART_FOUND}
148+
imageType={ImageType.Medium}
149+
/>
150+
</div>
151+
) : (
152+
<div ref={scrollableRef} className="dc__overflow-auto flexbox-col flex-grow-1 mxh-350">
153+
{filteredChartList.map(
154+
(list, index) =>
155+
list.id !== 1 && (
156+
<ChartListPopUpRow
157+
key={list.name}
158+
index={index}
159+
list={list}
160+
enabled={chartActiveMap[list.name]}
161+
toggleEnabled={toggleEnabled(list.name)}
162+
/>
163+
),
164+
)}
165+
<div className="p-16" style={{ marginTop: 'auto' }}>
166+
<InfoBlock
167+
variant="help"
168+
description={
169+
<div>
170+
<span>
171+
Showing Chart repositories and OCI Registries (used as chart repositories).
172+
You can add other&nbsp;
173+
</span>
174+
<NavLink to={URLS.GLOBAL_CONFIG_CHART}>Chart repositories</NavLink>
175+
<span>&nbsp;or&nbsp;</span>
176+
<NavLink to={URLS.GLOBAL_CONFIG_DOCKER}>OCI Registries</NavLink>
177+
<span>&nbsp;as chart sources.</span>
178+
</div>
179+
}
180+
/>
181+
</div>
182+
</div>
183+
)}
184+
</>
185+
)
186+
}
187+
188+
return (
189+
<Popover
190+
open={open}
191+
overlayProps={overlayProps}
192+
popoverProps={popoverProps}
193+
triggerProps={triggerProps}
194+
triggerElement={null}
195+
buttonProps={{
196+
onClick: handleSourceBtnClick,
197+
text: 'source',
198+
variant: ButtonVariantType.secondary,
199+
size: ComponentSizeType.xxs,
200+
dataTestId: 'chart-store-source-button',
201+
startIcon: <Icon name="ic-input" color={null} />,
202+
}}
203+
>
204+
<div className="charts-list flexbox-col mh-400">
205+
<div className="flex dc__content-space px-16 pt-12 pb-11 border__primary--bottom">
206+
<h4 className="m-0 fs-14 lh-1-5 fw-6">Helm chart sources</h4>
207+
<div className="flex dc__gap-12">
208+
<AddChartSource text="Add" />
209+
<Button
210+
isLoading={isFetching}
211+
icon={<Icon name="ic-arrows-clockwise" color={null} />}
212+
size={ComponentSizeType.xxs}
213+
variant={ButtonVariantType.borderLess}
214+
dataTestId="chart-store-refetch-button"
215+
ariaLabel="Refetch charts"
216+
onClick={handleRefetchCharts}
217+
/>
218+
<div className="divider__primary" />
219+
<Button
220+
icon={<Icon name="ic-close-large" size={16} color={null} />}
221+
onClick={closePopover}
222+
size={ComponentSizeType.xxs}
223+
variant={ButtonVariantType.borderLess}
224+
dataTestId="chart-store-close-button"
225+
showAriaLabelInTippy={false}
226+
ariaLabel="Close"
227+
style={ButtonStyleType.negativeGrey}
228+
/>
229+
</div>
230+
</div>
231+
{renderChartListBody()}
232+
</div>
233+
</Popover>
234+
)
235+
}

0 commit comments

Comments
 (0)