diff --git a/web/src/components/autoobserve/live/AllSuggestionsDrawer.spec.ts b/web/src/components/autoobserve/live/AllSuggestionsDrawer.spec.ts index 07138af4c9..e340748ba9 100644 --- a/web/src/components/autoobserve/live/AllSuggestionsDrawer.spec.ts +++ b/web/src/components/autoobserve/live/AllSuggestionsDrawer.spec.ts @@ -244,4 +244,60 @@ describe("AllSuggestionsDrawer (journey 3)", () => { expect(wrapper.find(COUNT).text()).toBe("24"); expect(wrapper.findComponent(OTable).props("loading")).toBe(false); }); + + it("never shows the previous org's held total when the org switches mid-clamp", async () => { + api.mockResolvedValueOnce(page(["a1"], 30)); + const wrapper = mountDrawer(); + await flushPromises(); + api.mockResolvedValueOnce(page([], 24)); + let release: (value: unknown) => void = () => undefined; + api.mockReturnValueOnce(new Promise((resolve) => (release = resolve)) as never); + wrapper.findComponent(OTable).vm.$emit("pagination-change", { page: 2, pageSize: 25 }); + await flushPromises(); + expect(wrapper.find(COUNT).text()).toBe("24"); + api.mockResolvedValueOnce(page(["b1"], 1)); + setOrg("org-b"); + await flushPromises(); + expect(rows(wrapper).map((r) => r.id)).toEqual(["b1"]); + expect(wrapper.find(COUNT).text()).toBe("1"); + expect(wrapper.findComponent(OTable).props("loading")).toBe(false); + release(page(["a1"], 24)); + await flushPromises(); + expect(rows(wrapper).map((r) => r.id)).toEqual(["b1"]); + expect(wrapper.find(COUNT).text()).toBe("1"); + expect(wrapper.findComponent(OTable).props("loading")).toBe(false); + }); + + it("keeps the new org's own clamp when the old org's clamp refetch settles late", async () => { + const held = () => { + let release: (value: unknown) => void = () => undefined; + const promise = new Promise((resolve) => (release = resolve)); + return { promise: promise as never, release: (v: unknown) => release(v) }; + }; + const toPage2 = () => + wrapper.findComponent(OTable).vm.$emit("pagination-change", { page: 2, pageSize: 25 }); + api.mockResolvedValueOnce(page(["a1"], 30)); + const wrapper = mountDrawer(); + await flushPromises(); + const orgA = held(); + api.mockResolvedValueOnce(page([], 24)).mockReturnValueOnce(orgA.promise); + toPage2(); + await flushPromises(); + api.mockResolvedValueOnce(page(["b1"], 30)); + setOrg("org-b"); + await flushPromises(); + const orgB = held(); + api.mockResolvedValueOnce(page([], 20)).mockReturnValueOnce(orgB.promise); + toPage2(); + await flushPromises(); + expect(wrapper.find(COUNT).text()).toBe("20"); + orgA.release(page(["a1"], 24)); + await flushPromises(); + expect(wrapper.find(COUNT).text()).toBe("20"); + expect(wrapper.findComponent(OTable).props("loading")).toBe(true); + orgB.release(page(["b1"], 20)); + await flushPromises(); + expect(wrapper.find(COUNT).text()).toBe("20"); + expect(wrapper.findComponent(OTable).props("loading")).toBe(false); + }); }); diff --git a/web/src/components/autoobserve/live/AllSuggestionsDrawer.vue b/web/src/components/autoobserve/live/AllSuggestionsDrawer.vue index 2f161c8ca6..1bfa275288 100644 --- a/web/src/components/autoobserve/live/AllSuggestionsDrawer.vue +++ b/web/src/components/autoobserve/live/AllSuggestionsDrawer.vue @@ -177,6 +177,8 @@ const rows = computed(() => toSuggestionRows(t, zone.value, query.data.value?.suggestions ?? []), ); const clampTotal = ref(null); +// An org switch voids the hold, so an older org's clamp can never set or clear the newer org's total. +let clampRun = 0; const total = computed(() => clampTotal.value ?? query.data.value?.total ?? 0); const skeleton = useHeldSkeleton(() => props.open && state.value.status === "loading"); const paging = computed(() => query.isPlaceholderData.value && query.isFetching.value); @@ -202,16 +204,19 @@ watch( watch(org, () => { page.value = 1; + clampRun += 1; + clampTotal.value = null; }); // The page it lands on may be cached from before the total shrank, so it is fetched again under the new total. watch(pastEnd, async (past) => { if (!past) return; + const run = ++clampRun; clampTotal.value = total.value; page.value = Math.max(1, Math.ceil(clampTotal.value / DRAWER_PAGE)); await nextTick(); if (!query.isPlaceholderData.value) await query.refetch(); - clampTotal.value = null; + if (run === clampRun) clampTotal.value = null; }); const columns = computed[]>(() => [