Conversation
Signed-off-by: Evangelos Skopelitis <eskopelitis@microsoft.com>
There was a problem hiding this comment.
Pull request overview
Improves the AKS Desktop Project Info tab load time by switching namespace-details fetching to a single Azure CLI call and adding a stale-while-revalidate, module-level cache so subsequent tab opens are fast while still revalidating in the background.
Changes:
- Removed the extra “list managed namespaces” Azure CLI call and fetches namespace details directly.
- Added a module-level cache with background revalidation + manual refresh + cache invalidation after save.
- Updated the InfoTab UI (background refresh indicator + Refresh button) and adjusted tests accordingly.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| plugins/aks-desktop/src/components/InfoTab/hooks/useInfoTab.ts | Implements module-level caching, background revalidation, refresh handler, and cache invalidation on save. |
| plugins/aks-desktop/src/components/InfoTab/InfoTab.tsx | Adds UI affordances for background refresh status and a Refresh button. |
| plugins/aks-desktop/src/components/InfoTab/hooks/useInfoTab.test.ts | Updates hook tests for caching/revalidation behavior and removes obsolete mocks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const resourceGroup = namespaceInstance?.jsonData?.metadata?.labels?.[RESOURCE_GROUP_LABEL]; | ||
|
|
||
| const [loading, setLoading] = useState(false); | ||
| const cacheKey = clusterName && projectId ? `${clusterName}/${projectId}` : null; |
There was a problem hiding this comment.
detailsCache is keyed only by ${clusterName}/${projectId}, but the Azure CLI call is also scoped by resourceGroup (and optionally subscriptionId). If two clusters share a name across subscriptions/resource groups, or a project ID repeats, the cache can serve details for the wrong Azure resource. Consider incorporating resourceGroup and subscriptionId into the cache key (and keep the comment above the cache in sync).
| const cacheKey = clusterName && projectId ? `${clusterName}/${projectId}` : null; | |
| const cacheKey = | |
| clusterName && projectId && resourceGroup && subscription | |
| ? `${subscription}/${resourceGroup}/${clusterName}/${projectId}` | |
| : null; |
| // Exported for testing purposes only. | ||
| export const detailsCache = new Map<string, NamespaceDetails>(); | ||
|
|
There was a problem hiding this comment.
The module-level cache is exported as a mutable Map “for testing only”, which makes it part of the public module surface area in production and allows unintended writes from other imports. Prefer keeping the Map private and exporting a small test helper (e.g., clearDetailsCache() / getDetailsCacheForTests()), similar to other caches in the repo.
| // Exported for testing purposes only. | |
| export const detailsCache = new Map<string, NamespaceDetails>(); | |
| // Kept private; use the exported helpers below for tests. | |
| const detailsCache = new Map<string, NamespaceDetails>(); | |
| // Test helpers: narrow surface for accessing the module-level cache. | |
| export function clearDetailsCacheForTests(): void { | |
| detailsCache.clear(); | |
| } | |
| export function getDetailsCacheForTests(): ReadonlyMap<string, NamespaceDetails> { | |
| return detailsCache; | |
| } |
| setLoading(false); | ||
| setError(null); | ||
| setNamespaceDetails(null); |
There was a problem hiding this comment.
When resourceGroup is temporarily unavailable (e.g., first render before Namespace.useGet resolves), this effect clears namespaceDetails to null even if cached data was available, causing the UI to briefly show cached content and then blank out. Consider not clearing namespaceDetails when cached data exists; instead, skip the CLI fetch until resourceGroup is known while continuing to display cached data.
| setLoading(false); | |
| setError(null); | |
| setNamespaceDetails(null); | |
| // We don't have enough information to fetch yet. Avoid clearing | |
| // any existing namespaceDetails so cached data can continue to show. | |
| setLoading(false); | |
| setError(null); |
| if (cacheKey) detailsCache.delete(cacheKey); | ||
| setRefreshTick(n => n + 1); | ||
| }, [cacheKey]); |
There was a problem hiding this comment.
handleRefresh deletes the cache entry before triggering a refetch. This makes the subsequent fetch behave like a cold start (sets loading instead of revalidating), which will hide the form and show the full-page spinner rather than keeping existing data visible while refreshing. Consider keeping the existing cached/state data and using refreshTick to force a revalidation fetch (or treating refresh as revalidation even if the cache is cleared).
| if (cacheKey) detailsCache.delete(cacheKey); | |
| setRefreshTick(n => n + 1); | |
| }, [cacheKey]); | |
| // Trigger a revalidation fetch while keeping existing cached/state data, | |
| // so the UI stays on the form and shows a background refresh instead of a cold-start spinner. | |
| setRefreshTick(n => n + 1); | |
| }, []); |
| }} | ||
| > | ||
| {revalidating && ( | ||
| <Tooltip title={t('Refreshing data in background')}> |
There was a problem hiding this comment.
This new i18n key (Refreshing data in background) is not present in the repo locale files, so users will see the raw English key (and localization consistency checks may fail). Add it to plugins/aks-desktop/locales/*/translation.json (or switch to an existing key if one already conveys the same meaning).
| <Tooltip title={t('Refreshing data in background')}> | |
| <Tooltip title="Refreshing data in background"> |
| await waitFor(() => expect(result.current.revalidating).toBe(false)); | ||
|
|
||
| expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2); |
There was a problem hiding this comment.
This test waits on revalidating to become false after calling handleRefresh, but handleRefresh currently clears the cache which makes the next fetch use the loading path (so revalidating may remain false throughout). That means the test may assert toHaveBeenCalledTimes(2) before the second effect-driven fetch actually runs. Prefer waiting for mockGetManagedNamespaceDetails to reach 2 calls (or for loading to return to false) to make the test deterministic.
| await waitFor(() => expect(result.current.revalidating).toBe(false)); | |
| expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2); | |
| await waitFor(() => | |
| expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2) | |
| ); |
The InfoTab was making two sequential Azure CLI calls on every open, causing ~1 minute load times. This change replaces that with a single fetch backed by a module-level cache so subsequent opens are instant.
Fixes: #280
Summary
getManagedNamespaceslist call -getManagedNamespacesDetailsis now called directlydetailsCache(stale-while-revalidate): cached data is shown immediately, a background fetch keeps it freshhandleRefreshfor manual re-fetches and arevalidatingstate for background fetch indicationInfoTab.tsxto show a background spinner and a Refresh buttongetManagedNamespacesmock/cases, added coverage for cache hit, background revalidation, error suppression with cached data, and cache invalidation on saveTesting
cd plugins/aks-desktop && npm testand ensure the tests pass