Dedup checkout status refetch and in-flight requests
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
export type CheckoutStatusRevalidationParams = {
|
||||
serverId: string;
|
||||
agentId: string;
|
||||
isOpen: boolean;
|
||||
explorerTab: string;
|
||||
};
|
||||
|
||||
export function checkoutStatusRevalidationKey(params: CheckoutStatusRevalidationParams): string | null {
|
||||
if (!params.agentId) return null;
|
||||
if (!params.isOpen) return null;
|
||||
if (params.explorerTab !== "changes") return null;
|
||||
return `${params.serverId}:${params.agentId}`;
|
||||
}
|
||||
|
||||
export function nextCheckoutStatusRefetchDecision(
|
||||
prevKey: string | null,
|
||||
nextKey: string | null
|
||||
): { nextSeenKey: string | null; shouldRefetch: boolean } {
|
||||
if (!nextKey) return { nextSeenKey: null, shouldRefetch: false };
|
||||
if (prevKey === nextKey) return { nextSeenKey: prevKey, shouldRefetch: false };
|
||||
return { nextSeenKey: nextKey, shouldRefetch: true };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { checkoutStatusRevalidationKey, nextCheckoutStatusRefetchDecision } from "./checkout-status-revalidation";
|
||||
|
||||
describe("useCheckoutStatusQuery", () => {
|
||||
describe("checkoutStatusRevalidationKey", () => {
|
||||
it("returns null when sidebar is closed", () => {
|
||||
expect(
|
||||
checkoutStatusRevalidationKey({
|
||||
serverId: "daemon-1",
|
||||
agentId: "agent-1",
|
||||
isOpen: false,
|
||||
explorerTab: "changes",
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when tab is not changes", () => {
|
||||
expect(
|
||||
checkoutStatusRevalidationKey({
|
||||
serverId: "daemon-1",
|
||||
agentId: "agent-1",
|
||||
isOpen: true,
|
||||
explorerTab: "files",
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("returns a stable key when open and on changes tab", () => {
|
||||
expect(
|
||||
checkoutStatusRevalidationKey({
|
||||
serverId: "daemon-1",
|
||||
agentId: "agent-1",
|
||||
isOpen: true,
|
||||
explorerTab: "changes",
|
||||
})
|
||||
).toBe("daemon-1:agent-1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("nextCheckoutStatusRefetchDecision", () => {
|
||||
it("refetches only once per key until reset", () => {
|
||||
const key = "daemon-1:agent-1";
|
||||
|
||||
expect(nextCheckoutStatusRefetchDecision(null, key)).toEqual({
|
||||
nextSeenKey: key,
|
||||
shouldRefetch: true,
|
||||
});
|
||||
|
||||
expect(nextCheckoutStatusRefetchDecision(key, key)).toEqual({
|
||||
nextSeenKey: key,
|
||||
shouldRefetch: false,
|
||||
});
|
||||
|
||||
expect(nextCheckoutStatusRefetchDecision(key, null)).toEqual({
|
||||
nextSeenKey: null,
|
||||
shouldRefetch: false,
|
||||
});
|
||||
|
||||
expect(nextCheckoutStatusRefetchDecision(null, key)).toEqual({
|
||||
nextSeenKey: key,
|
||||
shouldRefetch: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("refetches again when agent changes while active", () => {
|
||||
expect(
|
||||
nextCheckoutStatusRefetchDecision("daemon-1:agent-1", "daemon-1:agent-2")
|
||||
).toEqual({
|
||||
nextSeenKey: "daemon-1:agent-2",
|
||||
shouldRefetch: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { UnistylesRuntime } from "react-native-unistyles";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
||||
import {
|
||||
checkoutStatusRevalidationKey,
|
||||
nextCheckoutStatusRefetchDecision,
|
||||
} from "./checkout-status-revalidation";
|
||||
|
||||
const CHECKOUT_STATUS_STALE_TIME = 15_000;
|
||||
|
||||
@@ -48,12 +52,17 @@ export function useCheckoutStatusQuery({ serverId, agentId }: UseCheckoutStatusQ
|
||||
});
|
||||
|
||||
// Revalidate when sidebar is open with "changes" tab active.
|
||||
const revalidationKey = useMemo(
|
||||
() => checkoutStatusRevalidationKey({ serverId, agentId, isOpen, explorerTab }),
|
||||
[serverId, agentId, isOpen, explorerTab]
|
||||
);
|
||||
const lastRevalidationKey = useRef<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (!isOpen || explorerTab !== "changes" || !agentId) {
|
||||
return;
|
||||
}
|
||||
const decision = nextCheckoutStatusRefetchDecision(lastRevalidationKey.current, revalidationKey);
|
||||
lastRevalidationKey.current = decision.nextSeenKey;
|
||||
if (!decision.shouldRefetch) return;
|
||||
void query.refetch();
|
||||
}, [isOpen, explorerTab, agentId, query]);
|
||||
}, [revalidationKey, query.refetch]);
|
||||
|
||||
return {
|
||||
status: query.data ?? null,
|
||||
|
||||
Reference in New Issue
Block a user