Skip to content

Commit 5fb78da

Browse files
authored
Merge pull request #2820 from devtron-labs/feat/revamp-chart-store-details
feat: Revamp Chart Store Details
2 parents 5d2d8d4 + 3998907 commit 5fb78da

38 files changed

+1908
-1913
lines changed

.eslintignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,10 @@ src/components/charts/ChartGroupUpdate.tsx
143143
src/components/charts/ChartHeaderFilters.tsx
144144
src/components/charts/Charts.tsx
145145
src/components/charts/MultiChartSummary.tsx
146-
src/components/charts/SavedValues/SavedValuesList.tsx
147146
src/components/charts/chartValues/ChartValues.tsx
148147
src/components/charts/charts.service.ts
149148
src/components/charts/charts.util.tsx
150149
src/components/charts/dialogs/ValuesYamlConfirmDialog.tsx
151-
src/components/charts/discoverChartDetail/About.tsx
152-
src/components/charts/discoverChartDetail/ChartDeploymentList.tsx
153-
src/components/charts/discoverChartDetail/DiscoverChartDetails.tsx
154150
src/components/charts/list/ChartGroup.tsx
155151
src/components/charts/list/ChartListPopUpRow.tsx
156152
src/components/charts/list/DeployedChartFilters.tsx

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"homepage": "/dashboard",
66
"dependencies": {
7-
"@devtron-labs/devtron-fe-common-lib": "1.17.0-pre-11",
7+
"@devtron-labs/devtron-fe-common-lib": "1.17.0-pre-12",
88
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
99
"@rjsf/core": "^5.13.3",
1010
"@rjsf/utils": "^5.13.3",
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { useEffect, useMemo, useState } from 'react'
2+
import { Route, Switch, useRouteMatch } from 'react-router-dom'
3+
4+
import {
5+
APIResponseHandler,
6+
BreadCrumb,
7+
handleAnalyticsEvent,
8+
PageHeader,
9+
SegmentedControl,
10+
SegmentedControlProps,
11+
SelectPickerProps,
12+
useAsync,
13+
useBreadcrumb,
14+
useUrlFilters,
15+
} from '@devtron-labs/devtron-fe-common-lib'
16+
17+
import { ChartSelector } from '@Components/AppSelector'
18+
import { getChartSelectAPI } from '@Components/charts/charts.util'
19+
import { URLS } from '@Config/routes'
20+
21+
import { ChartDetailsAbout } from './ChartDetailsAbout'
22+
import { ChartDetailsDeploy } from './ChartDetailsDeploy'
23+
import { ChartDetailsDeployments } from './ChartDetailsDeployments'
24+
import { ChartDetailsPresetValues } from './ChartDetailsPresetValues'
25+
import { ChartDetailsReadme } from './ChartDetailsReadme'
26+
import { CHART_DETAILS_PORTAL_CONTAINER_ID, CHART_DETAILS_SEGMENTS } from './constants'
27+
import { fetchChartDetails, fetchChartVersions } from './services'
28+
import { ChartDetailsRouteParams, ChartDetailsSearchParams, ChartDetailsSegment } from './types'
29+
import { chartSelectorFilterOption, chartSelectorFormatOptionLabel, parseChartDetailsSearchParams } from './utils'
30+
31+
import './chartDetails.scss'
32+
33+
export const ChartDetails = () => {
34+
// STATES
35+
const [selectedChartVersion, setSelectedChartVersion] = useState<number>(null)
36+
37+
// HOOKS
38+
const {
39+
path,
40+
params: { chartId },
41+
} = useRouteMatch<ChartDetailsRouteParams>()
42+
43+
const { tab, updateSearchParams } = useUrlFilters<void, ChartDetailsSearchParams>({
44+
parseSearchParams: parseChartDetailsSearchParams,
45+
})
46+
47+
// ASYNC CALLS
48+
const [isFetchingChartVersions, chartVersions, chartVersionsErr, reloadChartVersions] = useAsync(
49+
() => fetchChartVersions(chartId),
50+
[chartId],
51+
)
52+
53+
const [isFetchingChartDetails, chartDetails, chartDetailsErr, reloadChartDetails] = useAsync(
54+
() => fetchChartDetails(selectedChartVersion),
55+
[selectedChartVersion],
56+
!!selectedChartVersion,
57+
{ resetOnChange: false },
58+
)
59+
60+
useEffect(() => {
61+
if (!isFetchingChartVersions && chartVersions?.length) {
62+
setSelectedChartVersion(chartVersions[0].id)
63+
}
64+
}, [isFetchingChartVersions, chartVersions])
65+
66+
// CONFIGS
67+
const { breadcrumbs } = useBreadcrumb(
68+
{
69+
alias: {
70+
':chartSegment?': null,
71+
':chartId': {
72+
component: (
73+
<ChartSelector
74+
primaryKey="chartId"
75+
primaryValue="name"
76+
api={getChartSelectAPI}
77+
matchedKeys={[]}
78+
apiPrimaryKey="id"
79+
formatOptionLabel={chartSelectorFormatOptionLabel}
80+
filterOption={chartSelectorFilterOption}
81+
/>
82+
),
83+
linked: false,
84+
},
85+
chart: null,
86+
'chart-store': null,
87+
},
88+
},
89+
[chartId],
90+
)
91+
92+
const chartOptions = useMemo(
93+
() =>
94+
(chartVersions ?? []).map(({ id, version }) => ({
95+
label: version.startsWith('v') ? version : `v${version}`,
96+
value: id,
97+
})),
98+
[JSON.stringify(chartVersions)],
99+
)
100+
101+
// CONSTANTS
102+
const isChartDetailsLoading = isFetchingChartDetails || !chartDetails
103+
104+
// HANDLERS
105+
const handleSegmentChange: SegmentedControlProps['onChange'] = (selectedSegment) => {
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 })
113+
}
114+
115+
const handleChartChange: SelectPickerProps<number>['onChange'] = ({ value }) => {
116+
setSelectedChartVersion(value)
117+
}
118+
119+
// RENDERERS
120+
const renderBreadcrumbs = () => <BreadCrumb breadcrumbs={breadcrumbs} />
121+
122+
const renderSegments = () => {
123+
switch (tab) {
124+
case ChartDetailsSegment.README:
125+
return (
126+
<ChartDetailsReadme
127+
chartName={chartDetails?.name}
128+
readme={chartDetails?.readme}
129+
chartsOptions={chartOptions}
130+
selectedChartVersion={selectedChartVersion}
131+
onChartChange={handleChartChange}
132+
isLoading={isChartDetailsLoading}
133+
error={chartDetailsErr}
134+
reload={reloadChartDetails}
135+
/>
136+
)
137+
case ChartDetailsSegment.PRESET_VALUES:
138+
return <ChartDetailsPresetValues />
139+
case ChartDetailsSegment.DEPLOYMENTS:
140+
return <ChartDetailsDeployments chartIcon={chartDetails?.icon} />
141+
default:
142+
return null
143+
}
144+
}
145+
146+
return (
147+
<div className="flex-grow-1 flexbox-col bg__secondary dc__overflow-hidden">
148+
<PageHeader isBreadcrumbs breadCrumbs={renderBreadcrumbs} />
149+
150+
<APIResponseHandler
151+
isLoading={isFetchingChartVersions}
152+
progressingProps={{ pageLoader: true }}
153+
error={chartVersionsErr}
154+
errorScreenManagerProps={{ code: chartVersionsErr?.code, reload: reloadChartVersions }}
155+
>
156+
<Switch>
157+
<Route path={`${path}${URLS.DEPLOY_CHART}/:presetValueId?`}>
158+
<ChartDetailsDeploy
159+
chartDetails={chartDetails}
160+
chartVersions={chartVersions}
161+
selectedChartVersion={selectedChartVersion}
162+
/>
163+
</Route>
164+
<Route>
165+
<div className="chart-details flex-grow-1 p-20 dc__overflow-auto">
166+
<div className="flexbox-col dc__gap-16 mw-none">
167+
<div id={CHART_DETAILS_PORTAL_CONTAINER_ID} className="flex dc__content-space">
168+
<SegmentedControl
169+
name="chart-details-segmented-control"
170+
segments={CHART_DETAILS_SEGMENTS}
171+
value={tab}
172+
onChange={handleSegmentChange}
173+
/>
174+
</div>
175+
{renderSegments()}
176+
</div>
177+
<ChartDetailsAbout isLoading={isChartDetailsLoading} chartDetails={chartDetails} />
178+
</div>
179+
</Route>
180+
</Switch>
181+
</APIResponseHandler>
182+
</div>
183+
)
184+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { useMemo } from 'react'
2+
import { useRouteMatch } from 'react-router-dom'
3+
import moment from 'moment'
4+
5+
import {
6+
Button,
7+
ButtonComponentType,
8+
ComponentSizeType,
9+
handleAnalyticsEvent,
10+
Icon,
11+
ImageWithFallback,
12+
InfoBlock,
13+
} from '@devtron-labs/devtron-fe-common-lib'
14+
15+
import { ChartDetailsAboutProps } from './types'
16+
import { getParsedChartYAML } from './utils'
17+
18+
const AboutHeaderLoadingSkeleton = () => (
19+
<div className="flexbox-col dc__gap-16">
20+
<div className="shimmer-loading chart-details__loading-icon" />
21+
<div className="shimmer-loading w-150 h-20" />
22+
<div className="flexbox-col dc__gap-8">
23+
<div className="shimmer-loading w-250 h-16" />
24+
<div className="shimmer-loading w-100 h-16" />
25+
</div>
26+
</div>
27+
)
28+
29+
const AboutBodyLoadingSkeleton = () => (
30+
<div className="flexbox-col dc__gap-12">
31+
<div className="shimmer-loading w-250 h-16" />
32+
<div className="shimmer-loading w-200 h-16" />
33+
<div className="shimmer-loading w-100 h-16" />
34+
</div>
35+
)
36+
37+
const ChartMetaData = ({
38+
title,
39+
subtitle,
40+
isLink,
41+
}: {
42+
title: string
43+
subtitle: string | string[]
44+
isLink?: boolean
45+
}) => {
46+
const renderSubtitle = () => {
47+
if (Array.isArray(subtitle)) {
48+
return subtitle.map((item) =>
49+
isLink && item ? (
50+
<a
51+
key={item}
52+
className="m-0 fs-13 lh-20 fw-4 dc__break-word"
53+
href={item}
54+
target="_blank"
55+
rel="noreferrer"
56+
>
57+
{item}
58+
</a>
59+
) : (
60+
<p key={item} className="m-0 fs-13 lh-20 fw-4 cn-9 dc__break-word">
61+
{item}
62+
</p>
63+
),
64+
)
65+
}
66+
67+
return isLink && subtitle ? (
68+
<a className="m-0 fs-13 lh-20 fw-4 dc__break-word" href={subtitle} target="_blank" rel="noreferrer">
69+
{subtitle}
70+
</a>
71+
) : (
72+
<p className="m-0 fs-13 lh-20 fw-4 cn-9 dc__break-word">{subtitle || '-'}</p>
73+
)
74+
}
75+
76+
return (
77+
<div className="flexbox-col dc__gap-4">
78+
<h5 className="m-0 fs-13 lh-20 fw-4 cn-7">{title}</h5>
79+
{renderSubtitle()}
80+
</div>
81+
)
82+
}
83+
84+
export const ChartDetailsAbout = ({ chartDetails, isLoading }: ChartDetailsAboutProps) => {
85+
const { url } = useRouteMatch()
86+
87+
const {
88+
icon,
89+
name,
90+
description,
91+
chartName,
92+
created,
93+
appVersion,
94+
deprecated,
95+
digest,
96+
home,
97+
isOCICompliantChart,
98+
chartYaml,
99+
source,
100+
} = chartDetails ?? {}
101+
102+
const parsedChartYaml = useMemo(() => getParsedChartYAML(chartYaml), [chartYaml])
103+
104+
const handleDeploy = () => {
105+
handleAnalyticsEvent({ category: 'Chart Store', action: 'CS_CHART_CONFIGURE_&_DEPLOY' })
106+
}
107+
108+
if (isLoading && !chartDetails) {
109+
return (
110+
<div className="flexbox-col dc__gap-20 mw-none">
111+
<AboutHeaderLoadingSkeleton />
112+
<div className="divider__secondary--horizontal" />
113+
<AboutBodyLoadingSkeleton />
114+
</div>
115+
)
116+
}
117+
118+
return (
119+
<div className="flexbox-col dc__gap-20 mw-none">
120+
<div className="flexbox-col dc__gap-12">
121+
<ImageWithFallback
122+
imageProps={{
123+
src: icon,
124+
alt: 'chart-icon',
125+
className: 'br-6',
126+
height: 48,
127+
width: 48,
128+
}}
129+
fallbackImage={<Icon name="ic-helm" color="N700" size={48} />}
130+
/>
131+
<h2 className="m-0 fs-16 lh-24 fw-6 cn-9">{name}</h2>
132+
<p className="m-0 fs-13 lh-20 cn-9">{description}</p>
133+
</div>
134+
<div>
135+
<Button
136+
dataTestId="deploy-chart-button"
137+
startIcon={<Icon name="ic-rocket-launch" color={null} />}
138+
text="Deploy Chart..."
139+
size={ComponentSizeType.medium}
140+
component={ButtonComponentType.link}
141+
linkProps={{
142+
to: `${url}/deploy-chart`,
143+
}}
144+
onClick={handleDeploy}
145+
/>
146+
</div>
147+
<div className="divider__secondary--horizontal" />
148+
{isLoading ? (
149+
<AboutBodyLoadingSkeleton />
150+
) : (
151+
<>
152+
{deprecated && (
153+
<InfoBlock
154+
variant="warning"
155+
heading="Deprecated Chart"
156+
description="This chart is no longer maintained and may not receive updates or support from the publisher."
157+
/>
158+
)}
159+
<div className="flexbox-col dc__gap-12">
160+
<ChartMetaData title="Chart source" subtitle={chartName} />
161+
<ChartMetaData title="Home" subtitle={home} isLink />
162+
<ChartMetaData
163+
title="Application Version"
164+
subtitle={appVersion?.startsWith('v') ? appVersion.slice(1) : appVersion}
165+
/>
166+
<ChartMetaData
167+
title="Created"
168+
subtitle={isOCICompliantChart ? '-' : moment(created).fromNow()}
169+
/>
170+
<ChartMetaData title="Digest" subtitle={digest} />
171+
{!!parsedChartYaml && (
172+
<ChartMetaData title="Source" subtitle={parsedChartYaml.sources || source} isLink />
173+
)}
174+
</div>
175+
</>
176+
)}
177+
</div>
178+
)
179+
}

0 commit comments

Comments
 (0)