@@ -127,6 +127,7 @@ const RemediatedVulnerabilitiesTabContent = ({
127127 onDataRefresh,
128128 selectedVulnerability,
129129 onSelectVulnerability,
130+ refreshKey,
130131} : {
131132 service : string
132133 image : string
@@ -137,6 +138,7 @@ const RemediatedVulnerabilitiesTabContent = ({
137138 onDataRefresh ?: ( vulnerability : string ) => void | Promise < void >
138139 selectedVulnerability : string | null
139140 onSelectVulnerability : ( cve : string | null ) => void
141+ refreshKey : number
140142} ) => {
141143 return (
142144 < >
@@ -194,6 +196,7 @@ const RemediatedVulnerabilitiesTabContent = ({
194196 vulnerability = { selectedVulnerability }
195197 onClose = { ( ) => onSelectVulnerability ( null ) }
196198 onRevertSuccess = { onDataRefresh }
199+ refreshKey = { refreshKey }
197200 />
198201 </ >
199202 )
@@ -237,7 +240,7 @@ export const ImageIssuesList = ({
237240 )
238241 const [ vulnerabilitiesSuccessMessage , setVulnerabilitiesSuccessMessage ] = useTimedState < string > ( 10000 )
239242 const [ pollErrorMessage , setPollErrorMessage ] = useTimedState < string > ( 10000 )
240- const [ , setRefreshKey ] = useState ( 0 )
243+ const [ refreshKey , setRefreshKey ] = useState ( 0 )
241244 const pollTimeoutsRef = useRef < ReturnType < typeof setTimeout > [ ] > ( [ ] )
242245 const isMountedRef = useRef ( true )
243246
@@ -251,6 +254,7 @@ export const ImageIssuesList = ({
251254
252255 const refreshIssuesData = useCallback (
253256 async ( vulnerability : string ) => {
257+ // Helper: match only queries for the currently viewed service + image.
254258 const matchesCurrentServiceAndImage = (
255259 filter : { service ?: string [ ] ; image ?: string [ ] ; repository ?: string [ ] ; vulnerability ?: string [ ] } | undefined
256260 ) =>
@@ -259,6 +263,7 @@ export const ImageIssuesList = ({
259263 ( ( Array . isArray ( filter ?. image ) && filter . image . includes ( image . repository ) ) ||
260264 ( Array . isArray ( filter ?. repository ) && filter . repository . includes ( image . repository ) ) )
261265
266+ // 1. Immediately refetch remediations for the affected CVE so the panel shows fresh data.
262267 await queryClient . refetchQueries ( {
263268 type : "all" ,
264269 predicate : ( query ) => {
@@ -275,6 +280,7 @@ export const ImageIssuesList = ({
275280 } ,
276281 } )
277282
283+ // 2. Immediately refetch images so the active/remediated tabs reflect the change.
278284 await queryClient . refetchQueries ( {
279285 type : "all" ,
280286 predicate : ( query ) => {
@@ -286,14 +292,13 @@ export const ImageIssuesList = ({
286292 } ,
287293 } )
288294
295+ // 3. Bump refreshKey so memoized promises (e.g. RemediationHistoryPanel) re-read from cache.
289296 setRefreshKey ( ( k ) => k + 1 )
290297
291- // Cancel any pending polls from a previous operation and schedule fresh ones.
292- // The backend takes ~5–6 min to propagate changes, so we poll at 2.5 and 5 min.
293- // Note: we intentionally use setTimeout + invalidateQueries here instead of
294- // React Query's refetchInterval. The entire data layer in this app uses
295- // queryClient.ensureQueryData() + React use() for Suspense-based data fetching,
296- // not useQuery hooks.
298+ // 4. Schedule background re-polls at 2.5 min and 5 min.
299+ // The backend takes ~5–6 min to propagate, so a second pass catches late updates.
300+ // Note: we use setTimeout + invalidateQueries (not refetchInterval) because the data
301+ // layer relies on ensureQueryData + React use() for Suspense, not useQuery hooks.
297302 pollTimeoutsRef . current . forEach ( clearTimeout )
298303 pollTimeoutsRef . current = [ ]
299304 ; [ 2.5 * 60 * 1000 , 5 * 60 * 1000 ] . forEach ( ( delay ) => {
@@ -417,6 +422,7 @@ export const ImageIssuesList = ({
417422 onDataRefresh = { refreshIssuesData }
418423 selectedVulnerability = { vulRemediations ?? null }
419424 onSelectVulnerability = { handleRemediationPanelVulnerabilityChange }
425+ refreshKey = { refreshKey }
420426 />
421427 </ TabPanel >
422428 </ Tabs >
0 commit comments