diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue
index 06ee9d2a10..4add0d6613 100644
--- a/apps/desktop/src/components/grid/DataGrid.vue
+++ b/apps/desktop/src/components/grid/DataGrid.vue
@@ -38,6 +38,7 @@ import {
PanelBottom,
PanelRight,
RefreshCw,
+ RefreshCcw,
TableProperties,
UserRound,
Database,
@@ -203,7 +204,7 @@ import {
isToggleTransposeShortcut,
} from "@/lib/editor/keyboardShortcuts";
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid/dataGridScrollGutter";
-import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
+import { canFetchNextDataGridSegment, canGoNextDataGridPage, dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, hasCompleteLocalDataGridResult, resolveDataGridPaginationTotal, showDataGridRerunTotalCountAction, type DataGridInexactTotalRowCountMode } from "@/lib/dataGrid/dataGridPagination";
import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions";
import {
createResultScopedPendingRequests,
@@ -3560,6 +3561,13 @@ watch(
);
const canCalculateTotalRowCount = computed(() => !!props.countTotalRows || (!!props.connectionId && (!!props.tableMeta || !!props.countSql)));
const showExactTotalCountAction = computed(() => canCalculateTotalRowCount.value && (totalRowCountIsExact.value === false || typeof displayedTotalRowCount.value !== "number"));
+const showRerunTotalCountAction = computed(() =>
+ showDataGridRerunTotalCountAction({
+ canCalculateTotalRowCount: canCalculateTotalRowCount.value,
+ displayedTotalRowCount: displayedTotalRowCount.value,
+ totalRowCountIsExact: totalRowCountIsExact.value,
+ }),
+);
watch(
[
() =>
@@ -13963,12 +13971,25 @@ function openGridSnapshot() {
})
}}
{{ t(totalRowCountLabelKey, { count: displayedTotalRowCount }) }}
-
+
{{ t("grid.totalRowCountLoading") }}
+
(truncated)
{{ t("grid.rowsAffected", { count: result.affected_rows }) }}
diff --git a/apps/desktop/src/components/grid/DataGridPagination.vue b/apps/desktop/src/components/grid/DataGridPagination.vue
index 1c4651e5e0..f26beb770c 100644
--- a/apps/desktop/src/components/grid/DataGridPagination.vue
+++ b/apps/desktop/src/components/grid/DataGridPagination.vue
@@ -93,7 +93,7 @@ function handlePageInputKeydown(event: KeyboardEvent) {
{{ t("grid.rows", { count: selectionSummary.rowCount }) }}
-
+
{{ t("grid.allLoaded") }}
diff --git a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts
index e1cf49ce40..457a43bf2e 100644
--- a/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts
+++ b/apps/desktop/src/components/grid/__tests__/DataGridSurfaces.spec.ts
@@ -250,6 +250,41 @@ describe("DataGridPagination", () => {
expect(summaryText).toContain("grid.rows");
});
+ it("keeps the loading icon mounted with a constant footprint and only animates while loading", async () => {
+ const mounted = mountComponent(DataGridPagination, {
+ selectionSummary: null,
+ selectionSummarySumText: "",
+ selectionSummaryAverageText: "",
+ loading: false,
+ infiniteScrollEnabled: false,
+ infiniteScrollAllLoaded: false,
+ pageSize: 100,
+ customPageSizeInput: "",
+ pageSizeMenuItems: [],
+ exportMenuItems: [],
+ currentPage: 1,
+ canGoNextPage: false,
+ canJumpLastPage: false,
+ });
+ // Stable within this file's mountComponent+findAll format: Loader2 is the only Icon
+ // combining text-muted-foreground with w-3 h-3 in the pagination bar. Filtering by
+ // both avoids matching a future muted icon without introducing data-testid.
+ const findLoader = () => findAll(mounted.root, (node) => node.props["data-stub"] === "Icon" && String(node.props.class).includes("text-muted-foreground") && String(node.props.class).includes("w-3 h-3"));
+
+ const idleLoaders = findLoader();
+ expect(idleLoaders).toHaveLength(1);
+ expect(String(idleLoaders[0].props.class)).toContain("invisible");
+ expect(String(idleLoaders[0].props.class)).not.toContain("animate-spin");
+ expect(String(idleLoaders[0].props["aria-hidden"])).toContain("true");
+
+ await mounted.setProps({ loading: true });
+ const busyLoaders = findLoader();
+ expect(busyLoaders).toHaveLength(1);
+ expect(String(busyLoaders[0].props.class)).toContain("animate-spin");
+ expect(String(busyLoaders[0].props.class)).not.toContain("invisible");
+ expect(String(busyLoaders[0].props["aria-hidden"])).toContain("true");
+ });
+
it("enforces first/previous/next/last disabled boundaries", async () => {
const firstPage = vi.fn();
const previousPage = vi.fn();
diff --git a/apps/desktop/src/components/grid/__tests__/DataGridTotalRowCount.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridTotalRowCount.spec.ts
index 8158106183..7eb4fdf627 100644
--- a/apps/desktop/src/components/grid/__tests__/DataGridTotalRowCount.spec.ts
+++ b/apps/desktop/src/components/grid/__tests__/DataGridTotalRowCount.spec.ts
@@ -1,9 +1,20 @@
import { describe, expect, it } from "vitest";
-import { dataGridTotalRowCountLabelKey, dataGridTruncationHintKey } from "@/lib/dataGrid/dataGridPagination";
+import { dataGridTotalRowCountLabelKey, dataGridTruncationHintKey, showDataGridRerunTotalCountAction } from "@/lib/dataGrid/dataGridPagination";
import DataGrid from "../DataGrid.vue";
type VuePropDefinition = { default?: unknown };
type VueComponentWithProps = { props?: Record };
+type VueComponentWithSsrRender = { ssrRender?: unknown };
+
+const statusRenderSource = (() => {
+ const component = DataGrid as unknown as VueComponentWithSsrRender;
+ return typeof component.ssrRender === "function" ? String(component.ssrRender) : "";
+})();
+
+const rerunBranch = (() => {
+ const start = statusRenderSource.indexOf("} else if ($setup.showRerunTotalCountAction) {");
+ return start === -1 ? "" : statusRenderSource.slice(start, statusRenderSource.indexOf("", start));
+})();
describe("DataGrid total row count exactness", () => {
it("uses a VictoriaMetrics-specific truncation explanation", () => {
@@ -24,3 +35,47 @@ describe("DataGrid total row count exactness", () => {
expect(dataGridTotalRowCountLabelKey(false, "estimated")).toBe("grid.totalRowCountEstimated");
});
});
+
+describe("DataGrid rerun total row count action", () => {
+ it("appears only for a displayed exact numeric total when counting is possible", () => {
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: 4200, totalRowCountIsExact: true })).toBe(true);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: 0, totalRowCountIsExact: true })).toBe(true);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, totalRowCountIsExact: true })).toBe(false);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: -1, totalRowCountIsExact: true })).toBe(false);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: false, displayedTotalRowCount: 4200, totalRowCountIsExact: true })).toBe(false);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: 4200, totalRowCountIsExact: false })).toBe(false);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: Infinity, totalRowCountIsExact: true })).toBe(false);
+ expect(showDataGridRerunTotalCountAction({ canCalculateTotalRowCount: true, displayedTotalRowCount: NaN, totalRowCountIsExact: true })).toBe(false);
+ });
+
+ it("keeps the numeric total outside the action chain and orders busy, inline link, then rerun icon", () => {
+ const numericTotal = statusRenderSource.indexOf('typeof $setup.displayedTotalRowCount === "number" && $setup.displayedTotalRowCount >= 0');
+ const busyBranch = statusRenderSource.indexOf("if ($setup.totalRowCountBusy && !($setup.showRerunTotalCountAction && $setup.manualTotalRowCountLoading)) {");
+ const inlineLink = statusRenderSource.indexOf("} else if ($setup.showExactTotalCountAction) {");
+ const rerunAction = statusRenderSource.indexOf("} else if ($setup.showRerunTotalCountAction) {");
+ expect(numericTotal).toBeGreaterThan(-1);
+ expect(busyBranch).toBeGreaterThan(numericTotal);
+ expect(inlineLink).toBeGreaterThan(busyBranch);
+ expect(rerunAction).toBeGreaterThan(inlineLink);
+ });
+
+ it("renders a spaced rerun icon labelled for the total-count action and disabled while counting", () => {
+ expect(rerunBranch).toContain('