mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-20 00:17:44 +00:00
fix(meilisearch): add index stats refresh with manual reload
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Copy, FileText, ListChecks, Settings } from "@lucide/vue";
|
||||
import { Copy, FileText, ListChecks, RefreshCcw, Settings } from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import type { MeilisearchIndexOverview } from "@/lib/backend/tauri";
|
||||
import { formatBytes } from "@/lib/database/serverMetrics";
|
||||
@@ -26,6 +27,8 @@ const { toast } = useToast();
|
||||
|
||||
const activeSection = ref<ActiveSection>(restoredUiState.activeSection ?? "documents");
|
||||
const overview = ref<MeilisearchIndexOverview | null>(null);
|
||||
const refreshing = ref(false);
|
||||
let refreshRequestId = 0;
|
||||
|
||||
trackUiState(() => ({ activeSection: activeSection.value }));
|
||||
|
||||
@@ -41,11 +44,17 @@ const updatedAtLabel = computed(() => {
|
||||
return formatMeilisearchTaskDateTime(value, locale.value);
|
||||
});
|
||||
|
||||
async function refreshStats() {
|
||||
async function refreshStats(notifyOnError = false) {
|
||||
const requestId = ++refreshRequestId;
|
||||
refreshing.value = true;
|
||||
try {
|
||||
overview.value = await api.meilisearchGetIndexOverview(props.connectionId, props.index);
|
||||
} catch {
|
||||
const nextOverview = await api.meilisearchGetIndexOverview(props.connectionId, props.index);
|
||||
if (requestId === refreshRequestId) overview.value = nextOverview;
|
||||
} catch (cause: any) {
|
||||
// Overview is best-effort; the tab still works without it.
|
||||
if (requestId === refreshRequestId && notifyOnError) toast(cause?.message || String(cause), 5000);
|
||||
} finally {
|
||||
if (requestId === refreshRequestId) refreshing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +78,12 @@ onMounted(() => {
|
||||
<!-- Left column: index meta + navigation -->
|
||||
<nav class="w-44 shrink-0 border-r flex flex-col gap-1 overflow-y-auto p-2">
|
||||
<div class="px-1 pb-2">
|
||||
<div class="truncate text-sm font-semibold text-foreground" :title="index">{{ index }}</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="min-w-0 flex-1 truncate text-sm font-semibold text-foreground" :title="index">{{ index }}</div>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 shrink-0" :disabled="refreshing" :title="t('meilisearch.refresh')" :aria-label="t('meilisearch.refresh')" @click="refreshStats(true)">
|
||||
<RefreshCcw class="h-3.5 w-3.5" :class="{ 'animate-spin': refreshing }" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="overview?.isIndexing" class="mt-0.5 text-xs text-primary">{{ t("meilisearch.isIndexing") }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
+48
-2
@@ -3,7 +3,7 @@
|
||||
import { createApp, defineComponent, h, nextTick, ref, type Component } from "vue";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({ getIndexOverview: vi.fn() }));
|
||||
const mocks = vi.hoisted(() => ({ getIndexOverview: vi.fn(), toast: vi.fn() }));
|
||||
|
||||
function passthrough(tag: string): Component {
|
||||
return defineComponent({
|
||||
@@ -21,10 +21,11 @@ vi.mock("@lucide/vue", () => ({
|
||||
Copy: passthrough("span"),
|
||||
FileText: passthrough("span"),
|
||||
ListChecks: passthrough("span"),
|
||||
RefreshCcw: passthrough("span"),
|
||||
Settings: passthrough("span"),
|
||||
}));
|
||||
vi.mock("@/lib/backend/api", () => ({ meilisearchGetIndexOverview: mocks.getIndexOverview }));
|
||||
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: vi.fn() }) }));
|
||||
vi.mock("@/composables/useToast", () => ({ useToast: () => ({ toast: mocks.toast }) }));
|
||||
vi.mock("@/lib/tabs/tabUiState", () => ({ useTabUiState: () => ({ initialState: {}, track: () => {} }) }));
|
||||
vi.mock("@/components/meilisearch/MeilisearchDocumentsPage.vue", () => ({ default: passthrough("div") }));
|
||||
vi.mock("@/components/meilisearch/MeilisearchSettingsPage.vue", () => ({ default: passthrough("div") }));
|
||||
@@ -105,3 +106,48 @@ describe("MeilisearchIndexView storage rows", () => {
|
||||
expect(text).toContain("25.7 MB");
|
||||
});
|
||||
});
|
||||
|
||||
describe("MeilisearchIndexView refresh", () => {
|
||||
const overview = {
|
||||
uid: "report_template",
|
||||
primaryKey: "id",
|
||||
createdAt: "2026-09-21T06:30:00Z",
|
||||
updatedAt: "2026-09-21T06:30:46Z",
|
||||
numberOfDocuments: 256,
|
||||
isIndexing: false,
|
||||
documentSize: 309_329_920,
|
||||
avgDocumentSize: 1_208_320,
|
||||
databaseSize: 4_194_304_000,
|
||||
};
|
||||
|
||||
function refreshButton(mounted: HTMLElement): HTMLButtonElement {
|
||||
const button = mounted.querySelector<HTMLButtonElement>('button[aria-label="meilisearch.refresh"]');
|
||||
expect(button).toBeTruthy();
|
||||
return button!;
|
||||
}
|
||||
|
||||
it("refetches the overview and renders the fresh numbers when clicked", async () => {
|
||||
mocks.getIndexOverview.mockResolvedValueOnce(overview).mockResolvedValueOnce({ ...overview, numberOfDocuments: 512 });
|
||||
|
||||
const mounted = await mountIndexView();
|
||||
expect(mounted.textContent).toContain("256");
|
||||
|
||||
refreshButton(mounted).click();
|
||||
await vi.waitFor(() => expect(mocks.getIndexOverview).toHaveBeenCalledTimes(2));
|
||||
expect(mocks.getIndexOverview).toHaveBeenNthCalledWith(2, "c1", "report_template");
|
||||
await vi.waitFor(() => expect(mounted.textContent).toContain("512"));
|
||||
expect(mounted.textContent).not.toContain("256");
|
||||
});
|
||||
|
||||
it("toasts when a manual refresh fails", async () => {
|
||||
mocks.getIndexOverview.mockResolvedValueOnce(overview).mockRejectedValueOnce(new Error("index unavailable"));
|
||||
|
||||
const mounted = await mountIndexView();
|
||||
expect(mocks.toast).not.toHaveBeenCalled();
|
||||
|
||||
refreshButton(mounted).click();
|
||||
await vi.waitFor(() => expect(mocks.toast).toHaveBeenCalledWith("index unavailable", 5000));
|
||||
// The mounted overview stays visible; the failure only notifies.
|
||||
expect(mounted.textContent).toContain("256");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user