diff --git a/web-admin/src/features/projects/status/ProjectResources.svelte b/web-admin/src/features/projects/status/ProjectResources.svelte index 2d5a7aa9cca..f78235b7b81 100644 --- a/web-admin/src/features/projects/status/ProjectResources.svelte +++ b/web-admin/src/features/projects/status/ProjectResources.svelte @@ -12,30 +12,31 @@ import ProjectResourcesTable from "./ProjectResourcesTable.svelte"; import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors"; import RefreshConfirmDialog from "./RefreshConfirmDialog.svelte"; + import { onDestroy } from "svelte"; const queryClient = useQueryClient(); const createTrigger = createRuntimeServiceCreateTrigger(); - let startRefetchInterval = false; let isRefreshConfirmDialogOpen = false; - let refetchKey = 0; + let maxRefetchAttempts = 60; // 30 seconds maximum + let refetchAttempts = 0; + let pollInterval: ReturnType | null = null; $: allResources = createRuntimeServiceListResources( $runtime.instanceId, - // All resource "kinds" undefined, { query: { select: (data) => { - // Exclude the "ProjectParser" resource + // Exclude the "ProjectParser" resource and "RefreshTrigger" resource return data.resources.filter( (resource) => - resource.meta.name.kind !== ResourceKind.ProjectParser, + resource.meta.name.kind !== ResourceKind.ProjectParser && + resource.meta.name.kind !== ResourceKind.RefreshTrigger, ); }, refetchOnMount: true, - refetchInterval: startRefetchInterval ? 500 : false, - queryKey: ["resources", refetchKey], + keepPreviousData: true, }, }, ); @@ -50,8 +51,36 @@ ), ); + function startPolling() { + stopPolling(); // Clear any existing interval + refetchAttempts = 0; + + pollInterval = setInterval(() => { + refetchAttempts++; + + if (refetchAttempts >= maxRefetchAttempts) { + stopPolling(); + return; + } + + void $allResources.refetch(); + }, 500); + } + + function stopPolling() { + if (pollInterval) { + clearInterval(pollInterval); + pollInterval = null; + } + refetchAttempts = 0; + } + + $: if (!isAnySourceOrModelReconciling) { + stopPolling(); + } + function refreshAllSourcesAndModels() { - startRefetchInterval = true; + startPolling(); void $createTrigger.mutateAsync({ instanceId: $runtime.instanceId, @@ -61,27 +90,19 @@ }); void queryClient.invalidateQueries( - getRuntimeServiceListResourcesQueryKey( - $runtime.instanceId, - // All resource "kinds" - undefined, - ), + getRuntimeServiceListResourcesQueryKey($runtime.instanceId, undefined), ); } - $: if (!isAnySourceOrModelReconciling) { - startRefetchInterval = false; - } - function triggerRefresh() { - // Force a new query - refetchKey++; - - startRefetchInterval = true; - - // Refetch the resources + startPolling(); void $allResources.refetch(); } + + // Cleanup on component destroy + onDestroy(() => { + stopPolling(); + });
@@ -92,23 +113,23 @@ on:click={() => { isRefreshConfirmDialogOpen = true; }} - disabled={startRefetchInterval} + disabled={Boolean(pollInterval)} > - {#if startRefetchInterval} + {#if pollInterval} Refreshing... {:else} Refresh all sources and models {/if} - {#if $allResources.isLoading} + {#if $allResources.isLoading && !$allResources.data} {:else if $allResources.isError}
Error loading resources: {$allResources.error?.message}
- {:else if $allResources.isSuccess} - + {:else if $allResources.data} + {/if}
diff --git a/web-common/src/features/entity-management/resource-selectors.ts b/web-common/src/features/entity-management/resource-selectors.ts index e1771ae4373..f1cd69cb5d8 100644 --- a/web-common/src/features/entity-management/resource-selectors.ts +++ b/web-common/src/features/entity-management/resource-selectors.ts @@ -27,6 +27,7 @@ export enum ResourceKind { Component = "rill.runtime.v1.Component", Canvas = "rill.runtime.v1.Canvas", API = "rill.runtime.v1.API", + RefreshTrigger = "rill.runtime.v1.RefreshTrigger", } export type UserFacingResourceKinds = Exclude<