From 90789315c35f64aebbdaaca1802f7e152c2e26b7 Mon Sep 17 00:00:00 2001 From: Evangelos Skopelitis Date: Thu, 11 Sep 2025 14:02:40 -0400 Subject: [PATCH 1/2] app-catalog: releases/List: Display current and latest app versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Evangelos Skopelitis Signed-off-by: René Dudfield --- app-catalog/src/api/charts.tsx | 33 +++++++++++++++++ app-catalog/src/components/releases/List.tsx | 37 +++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/app-catalog/src/api/charts.tsx b/app-catalog/src/api/charts.tsx index d5f27b74f..d100ad6c6 100644 --- a/app-catalog/src/api/charts.tsx +++ b/app-catalog/src/api/charts.tsx @@ -177,3 +177,36 @@ export async function fetchChartIcon(iconName: string) { getURLSearchParams(`${iconName}`); return request(url, { isJSON: false }, true, true, {}).then(response => response); } + +/** + * Fetches the latest application version for a given chart name from Artifact Hub. + * @param chartName - The name of the chart to fetch the latest version for. + * @returns A promise that resolves to the latest application version as a string. + */ +export async function fetchLatestAppVersion(chartName: string): Promise { + if (!chartName) { + return '—'; + } + + try { + const url = new URL('https://artifacthub.io/api/v1/packages/search'); + url.searchParams.set('offset', '0'); + url.searchParams.set('limit', '5'); + url.searchParams.set('facets', 'false'); + url.searchParams.set('kind', '0'); + url.searchParams.set('ts_query_web', chartName); + + const response = await fetch(url.toString()); + const dataResponse = await response.json(); + const packages: any[] = dataResponse?.packages ?? []; + + const lowerChartName = chartName.toLowerCase(); + const selectedPackage = packages.find( + p => p?.name?.toLowerCase() === lowerChartName || p?.normalized_name === lowerChartName + ); + + return selectedPackage?.app_version ?? '—'; + } catch { + return '—'; + } +} diff --git a/app-catalog/src/components/releases/List.tsx b/app-catalog/src/components/releases/List.tsx index 6b4588a1e..143f4bae9 100644 --- a/app-catalog/src/components/releases/List.tsx +++ b/app-catalog/src/components/releases/List.tsx @@ -8,8 +8,23 @@ import { } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; import { Box } from '@mui/material'; import { useEffect, useState } from 'react'; +import { fetchLatestAppVersion } from '../../api/charts'; import { listReleases } from '../../api/releases'; +/** + * @returns formatted version string + * @param v - version string + */ +function formatVersion(v?: string) { + const s = (v ?? '').trim(); + + if (!s || s === '—') { + return '—'; + } + + return s; +} + /** * ReleaseList component displays a list of installed Helm releases. * @@ -19,6 +34,7 @@ import { listReleases } from '../../api/releases'; */ export default function ReleaseList({ fetchReleases = listReleases }) { const [releases, setReleases] = useState | null>(null); + const [latestMap, setLatestMap] = useState>({}); useEffect(() => { fetchReleases().then(response => { @@ -30,6 +46,21 @@ export default function ReleaseList({ fetchReleases = listReleases }) { }); }, []); + useEffect(() => { + if (!releases?.length) { + setLatestMap({}); + return; + } + + Promise.all( + releases.map(async r => { + const chartName = r?.chart?.metadata?.name; + const v = chartName ? await fetchLatestAppVersion(chartName).catch(() => '—') : '—'; + return [r.name, v] as const; + }) + ).then(entries => setLatestMap(Object.fromEntries(entries))); + }, [releases]); + return ( release.namespace, }, { - label: 'App Version', + label: 'Current Version', getter: release => release.chart.metadata.appVersion, }, + { + label: 'Latest Version', + getter: release => formatVersion(latestMap[release?.name]), + }, { label: 'Version', getter: release => release.version, From 0e93b4a28a6f089a2ca5cc1e2aa4d601a9965fe6 Mon Sep 17 00:00:00 2001 From: Evangelos Skopelitis Date: Thu, 11 Sep 2025 14:03:24 -0400 Subject: [PATCH 2/2] app-catalog: releases/List: Format current app version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Evangelos Skopelitis Signed-off-by: René Dudfield --- app-catalog/src/components/releases/List.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-catalog/src/components/releases/List.tsx b/app-catalog/src/components/releases/List.tsx index 143f4bae9..c1023fbd5 100644 --- a/app-catalog/src/components/releases/List.tsx +++ b/app-catalog/src/components/releases/List.tsx @@ -95,7 +95,7 @@ export default function ReleaseList({ fetchReleases = listReleases }) { }, { label: 'Current Version', - getter: release => release.chart.metadata.appVersion, + getter: release => formatVersion(release.chart.metadata.appVersion), }, { label: 'Latest Version',