Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f90536c
Implement pagination support for model table sizes
royendo Jan 7, 2026
3298f69
models in status page
royendo Jan 7, 2026
718d6b3
Cache and eagerly subscribe to model size stores for persistent loading
royendo Jan 7, 2026
0510a42
Remove store caching to fix refresh showing empty sizes
royendo Jan 8, 2026
60d00b5
Add debugging and fix reactive columns for model sizes
royendo Jan 8, 2026
42c54d6
Make columns reactive to update when tableSizes changes
royendo Jan 8, 2026
2519cd4
Update ProjectResourcesTable.svelte
royendo Jan 8, 2026
a487787
prettier
royendo Jan 8, 2026
155a8ef
prettier
royendo Jan 8, 2026
3bc0475
separated tables,
royendo Jan 13, 2026
c716dff
Fix row count fetching with proper mutation pattern
royendo Jan 13, 2026
c6d1128
Fix row count query API usage - pass parameters at creation time
royendo Jan 13, 2026
2b47cc9
Add debug logging and fix row count query handling
royendo Jan 13, 2026
5f853ef
Expand row count query debugging to find response data structure
royendo Jan 13, 2026
734c7d5
Simplify row count fetching using direct queryServiceQuery function
royendo Jan 13, 2026
748cef3
Wait for JWT token availability before making row count queries
royendo Jan 13, 2026
273de0b
Move row count fetching to component level for proper JWT handling
royendo Jan 13, 2026
37c71ca
Use direct fetch with manual JWT header for row count queries
royendo Jan 13, 2026
be7b255
Switch row count fetching to use httpClient with JWT interceptor
royendo Jan 13, 2026
bbea399
removed RowCount issue with JWT, and Query select *
royendo Jan 13, 2026
86e1c04
e23
royendo Jan 13, 2026
ad7a94e
prettier
royendo Jan 13, 2026
327d3da
code qual + prettier
royendo Jan 13, 2026
641e182
updated e2e
royendo Jan 14, 2026
2e04920
no row count for now
royendo Jan 14, 2026
29a8d40
not sure if best practice but for e2e to locate
royendo Jan 14, 2026
98d1643
remove row_count, col_count
royendo Jan 16, 2026
479fb22
Merge branch 'main' into feat-model-size-status-page
royendo Jan 20, 2026
5b374ca
As Reviewed
royendo Jan 20, 2026
0813e09
better implementation
royendo Jan 20, 2026
fb367ba
prettier
royendo Jan 20, 2026
6a63658
code qual
royendo Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions web-admin/src/features/projects/status/ModelSizeCell.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import { formatMemorySize } from "@rilldata/web-common/lib/number-formatting/memory-size";

export let sizeBytes: string | number | undefined;

$: formattedSize = formatSize(sizeBytes);

function formatSize(bytes: string | number | undefined): string {
if (bytes === undefined || bytes === null || bytes === "-1") return "-";

let numBytes: number;
if (typeof bytes === "number") {
numBytes = bytes;
} else {
numBytes = parseInt(bytes, 10);
}

if (isNaN(numBytes) || numBytes < 0) return "-";
return formatMemorySize(numBytes);
}
</script>

<div class="truncate text-right tabular-nums">
<span class:text-gray-500={formattedSize === "-"}>
{formattedSize}
</span>
</div>
12 changes: 10 additions & 2 deletions web-admin/src/features/projects/status/ProjectResources.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
import Button from "web-common/src/components/button/Button.svelte";
import ProjectResourcesTable from "./ProjectResourcesTable.svelte";
import RefreshAllSourcesAndModelsConfirmDialog from "./RefreshAllSourcesAndModelsConfirmDialog.svelte";
import { useResources } from "./selectors";
import { useResources, useModelTableSizes } from "./selectors";
import { isResourceReconciling } from "@rilldata/web-admin/lib/refetch-interval-store";

const queryClient = useQueryClient();
const createTrigger = createRuntimeServiceCreateTrigger();

let isConfirmDialogOpen = false;
let tableSizes: any;

$: ({ instanceId } = $runtime);

$: resources = useResources(instanceId);
$: tableSizes = useModelTableSizes(instanceId, $resources.data?.resources);

$: hasReconcilingResources = $resources.data?.resources?.some(
isResourceReconciling,
Expand Down Expand Up @@ -65,7 +67,13 @@
Error loading resources: {$resources.error?.message}
</div>
{:else if $resources.data}
<ProjectResourcesTable data={$resources?.data?.resources} />
<ProjectResourcesTable
data={$resources?.data?.resources}
tableSizes={$tableSizes?.data ?? new Map()}
/>
{#if $tableSizes?.isLoading}
<div class="mt-2 text-xs text-gray-500">Loading model sizes...</div>
{/if}
{/if}
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
import type { ColumnDef } from "@tanstack/svelte-table";
import { flexRender } from "@tanstack/svelte-table";
import ActionsCell from "./ActionsCell.svelte";
import ModelSizeCell from "./ModelSizeCell.svelte";
import NameCell from "./NameCell.svelte";
import RefreshCell from "./RefreshCell.svelte";
import RefreshResourceConfirmDialog from "./RefreshResourceConfirmDialog.svelte";
import ResourceErrorMessage from "./ResourceErrorMessage.svelte";

export let data: V1Resource[];
export let tableSizes: Map<string, string | number> = new Map();

let isConfirmDialogOpen = false;
let dialogResourceName = "";
Expand Down Expand Up @@ -85,7 +87,7 @@
closeRefreshDialog();
};

// Create columns definition as a constant to prevent unnecessary re-creation
// Create columns definition as a constant - key block handles re-renders
const columns: ColumnDef<V1Resource, any>[] = [
{
accessorKey: "title",
Expand All @@ -104,6 +106,45 @@
name: getValue() as string,
}),
},
{
id: "size",
accessorFn: (row) => {
// Only for models
if (row.meta.name.kind !== ResourceKind.Model) return undefined;

const connector = row.model?.state?.resultConnector;
const tableName = row.model?.state?.resultTable;
if (!connector || !tableName) return undefined;

const key = `${connector}:${tableName}`;
return tableSizes.get(key);
},
header: "Size",
sortingFn: (rowA, rowB) => {
const sizeA = rowA.getValue("size") as string | number | undefined;
const sizeB = rowB.getValue("size") as string | number | undefined;

let numA = -1;
if (sizeA && sizeA !== "-1") {
numA = typeof sizeA === "number" ? sizeA : parseInt(sizeA, 10);
}

let numB = -1;
if (sizeB && sizeB !== "-1") {
numB = typeof sizeB === "number" ? sizeB : parseInt(sizeB, 10);
}

return numB - numA; // Descending
},
sortDescFirst: true,
cell: ({ getValue }) =>
flexRender(ModelSizeCell, {
sizeBytes: getValue() as string | number | undefined,
}),
meta: {
widthPercent: 0,
},
},
{
accessorFn: (row) => row.meta.reconcileStatus,
header: "Status",
Expand Down Expand Up @@ -190,11 +231,13 @@
);
</script>

<VirtualizedTable
data={tableData}
{columns}
columnLayout="minmax(95px, 108px) minmax(100px, 3fr) 48px minmax(80px, 2fr) minmax(100px, 2fr) 56px"
/>
{#key tableSizes}
<VirtualizedTable
data={tableData}
{columns}
columnLayout="minmax(95px, 108px) minmax(100px, 3fr) 100px 48px minmax(80px, 2fr) minmax(100px, 2fr) 56px"
/>
{/key}

<RefreshResourceConfirmDialog
bind:open={isConfirmDialogOpen}
Expand Down
199 changes: 199 additions & 0 deletions web-admin/src/features/projects/status/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import {
} from "@rilldata/web-admin/client";
import {
createRuntimeServiceListResources,
createConnectorServiceOLAPListTables,
type V1ListResourcesResponse,
type V1Resource,
} from "@rilldata/web-common/runtime-client";
import { ResourceKind } from "@rilldata/web-common/features/entity-management/resource-selectors";
import { createSmartRefetchInterval } from "@rilldata/web-admin/lib/refetch-interval-store";
import { readable } from "svelte/store";

export function useProjectDeployment(orgName: string, projName: string) {
return createAdminServiceGetProject<V1Deployment | undefined>(
Expand Down Expand Up @@ -47,3 +50,199 @@ export function useResources(instanceId: string) {
},
);
}

// Cache stores by instanceId and connector array to prevent recreating them
const modelSizesStoreCache = new Map<
string,
{ store: any; unsubscribe: () => void }
>();

// Keep preloaded query subscriptions alive so they don't get cancelled
const preloadedQuerySubscriptions = new Map<string, Set<() => void>>();

// Preload queries to ensure they start immediately and keep them alive
function preloadConnectorQueries(instanceId: string, connectorArray: string[]) {
const preloadKey = `${instanceId}:${connectorArray.join(",")}`;

// Only preload once per connector set
if (preloadedQuerySubscriptions.has(preloadKey)) {
return;
}

const subscriptions = new Set<() => void>();

for (const connector of connectorArray) {
const query = createConnectorServiceOLAPListTables(
{
instanceId,
connector,
},
{
query: {
enabled: true,
},
},
);

// Eagerly subscribe to keep the query alive
const unsubscribe = query.subscribe(() => {});
subscriptions.add(unsubscribe);
}

preloadedQuerySubscriptions.set(preloadKey, subscriptions);
}

function createCachedStore(
cacheKey: string,
instanceId: string,
connectorArray: string[],
) {
// Check if we already have a cached store
if (modelSizesStoreCache.has(cacheKey)) {
return modelSizesStoreCache.get(cacheKey)!.store;
}

// Preload queries immediately so they start running before store subscribers attach
preloadConnectorQueries(instanceId, connectorArray);

// If no connectors, return an empty readable store
if (connectorArray.length === 0) {
const emptyStore = readable(
{
data: new Map<string, string | number>(),
isLoading: false,
isError: false,
},
() => {},
);
modelSizesStoreCache.set(cacheKey, {
store: emptyStore,
unsubscribe: () => {},
});
return emptyStore;
}

// Create a new store with pagination support
const store = readable(
{
data: new Map<string, string | number>(),
isLoading: true,
isError: false,
},
(set) => {
const connectorTables = new Map<string, Array<any>>();
const connectorLoading = new Map<string, boolean>();
const connectorError = new Map<string, boolean>();
const subscriptions = new Set<() => void>();

const updateAndNotify = () => {
const sizeMap = new Map<string, string | number>();
let isLoading = false;
let isError = false;

for (const connector of connectorArray) {
if (connectorLoading.get(connector)) isLoading = true;
if (connectorError.get(connector)) isError = true;

for (const table of connectorTables.get(connector) || []) {
if (
table.name &&
table.physicalSizeBytes !== undefined &&
table.physicalSizeBytes !== null
) {
const key = `${connector}:${table.name}`;
sizeMap.set(key, table.physicalSizeBytes as string | number);
}
}
}

set({ data: sizeMap, isLoading, isError });
};

const fetchPage = (connector: string, pageToken?: string) => {
const query = createConnectorServiceOLAPListTables(
{
instanceId,
connector,
...(pageToken && { pageToken }),
} as any,
{
query: {
enabled: true,
},
},
);

const unsubscribe = query.subscribe((result: any) => {
connectorLoading.set(connector, result.isLoading);
connectorError.set(connector, result.isError);

if (result.data?.tables) {
const existing = connectorTables.get(connector) || [];
connectorTables.set(connector, [
...existing,
...result.data.tables,
]);
}

// If query completed and has more pages, fetch the next page
if (!result.isLoading && result.data?.nextPageToken) {
unsubscribe();
subscriptions.delete(unsubscribe);
fetchPage(connector, result.data.nextPageToken);
}

updateAndNotify();
});

subscriptions.add(unsubscribe);
};

// Start fetching for all connectors
for (const connector of connectorArray) {
connectorLoading.set(connector, true);
connectorError.set(connector, false);
connectorTables.set(connector, []);
fetchPage(connector);
}

return () => {
for (const unsub of subscriptions) {
unsub();
}
};
},
);

// Eagerly subscribe to keep queries alive across component re-renders
const unsubscribe = store.subscribe(() => {});
modelSizesStoreCache.set(cacheKey, { store, unsubscribe });

return store;
}

export function useModelTableSizes(
instanceId: string,
resources: V1Resource[] | undefined,
) {
// Extract unique connectors from model resources
const uniqueConnectors = new Set<string>();

if (resources) {
for (const resource of resources) {
if (resource?.meta?.name?.kind === ResourceKind.Model) {
const connector = resource.model?.state?.resultConnector;
const table = resource.model?.state?.resultTable;

if (connector && table) {
uniqueConnectors.add(connector);
}
}
}
}

const connectorArray = Array.from(uniqueConnectors).sort();
const cacheKey = `${instanceId}:${connectorArray.join(",")}`;

return createCachedStore(cacheKey, instanceId, connectorArray);
}
Loading