Skip to content

InfoTab: Add caching for namespace details#503

Open
skoeva wants to merge 1 commit intoAzure:mainfrom
skoeva:infotab
Open

InfoTab: Add caching for namespace details#503
skoeva wants to merge 1 commit intoAzure:mainfrom
skoeva:infotab

Conversation

@skoeva
Copy link
Copy Markdown
Collaborator

@skoeva skoeva commented Mar 21, 2026

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

  • Removed the redundant getManagedNamespaces list call - getManagedNamespacesDetails is now called directly
  • Added a module-level detailsCache (stale-while-revalidate): cached data is shown immediately, a background fetch keeps it fresh
  • Cache is invalidated after a successful save so stale data is never shown post-update
  • Added handleRefresh for manual re-fetches and a revalidating state for background fetch indication
  • Updated InfoTab.tsx to show a background spinner and a Refresh button
  • Updated tests: removed obsolete getManagedNamespaces mock/cases, added coverage for cache hit, background revalidation, error suppression with cached data, and cache invalidation on save

Testing

  • Run cd plugins/aks-desktop && npm test and ensure the tests pass

@skoeva skoeva self-assigned this Mar 21, 2026
@skoeva skoeva added the bug Something isn't working label Mar 21, 2026
Signed-off-by: Evangelos Skopelitis <eskopelitis@microsoft.com>
@skoeva skoeva marked this pull request as ready for review March 25, 2026 20:23
Copilot AI review requested due to automatic review settings March 25, 2026 20:23
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
const cacheKey = clusterName && projectId ? `${clusterName}/${projectId}` : null;
const cacheKey =
clusterName && projectId && resourceGroup && subscription
? `${subscription}/${resourceGroup}/${clusterName}/${projectId}`
: null;

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45
// Exported for testing purposes only.
export const detailsCache = new Map<string, NamespaceDetails>();

Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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;
}

Copilot uses AI. Check for mistakes.
Comment on lines 146 to 148
setLoading(false);
setError(null);
setNamespaceDetails(null);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);

Copilot uses AI. Check for mistakes.
Comment on lines +290 to +292
if (cacheKey) detailsCache.delete(cacheKey);
setRefreshTick(n => n + 1);
}, [cacheKey]);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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);
}, []);

Copilot uses AI. Check for mistakes.
}}
>
{revalidating && (
<Tooltip title={t('Refreshing data in background')}>
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
<Tooltip title={t('Refreshing data in background')}>
<Tooltip title="Refreshing data in background">

Copilot uses AI. Check for mistakes.
Comment on lines +207 to +209
await waitFor(() => expect(result.current.revalidating).toBe(false));

expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
await waitFor(() => expect(result.current.revalidating).toBe(false));
expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2);
await waitFor(() =>
expect(mockGetManagedNamespaceDetails).toHaveBeenCalledTimes(2)
);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Info Tab: Doesn't load, infinite loop

2 participants