| author | |
| committer | |
| log | b92c78f55215daf2a62d4cc6d06547029dc9df8f |
| tree | ee7dccaba595ef2aa9f3c542234752c6b7e4253d |
| parent | 801dfe5b413905553ef1a8ba7ee8b4d941c3fc24 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 84 insertions(+), 5 deletions(-)
apps/web/src/components/GitActionsControl.tsx+4-3| ... | @@ -47,6 +47,7 @@ import { | ... | @@ -47,6 +47,7 @@ import { |
| 47 | gitPullMutationOptions, | 47 | gitPullMutationOptions, |
| 48 | gitRunStackedActionMutationOptions, | 48 | gitRunStackedActionMutationOptions, |
| 49 | gitStatusQueryOptions, | 49 | gitStatusQueryOptions, |
| 50 | invalidateGitStatusQuery, | ||
| 50 | invalidateGitQueries, | 51 | invalidateGitQueries, |
| 51 | } from "~/lib/gitReactQuery"; | 52 | } from "~/lib/gitReactQuery"; |
| 52 | import { newCommandId, randomUUID } from "~/lib/utils"; | 53 | import { newCommandId, randomUUID } from "~/lib/utils"; |
| ... | @@ -286,8 +287,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions | ... | @@ -286,8 +287,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 286 | 287 | ||
| 287 | useEffect(() => { | 288 | useEffect(() => { |
| 288 | if (!isGitStatusOutOfSync) return; | 289 | if (!isGitStatusOutOfSync) return; |
| 289 | void invalidateGitQueries(queryClient); | 290 | void invalidateGitQueries(queryClient, { cwd: gitCwd }); |
| 290 | }, [isGitStatusOutOfSync, queryClient]); | 291 | }, [gitCwd, isGitStatusOutOfSync, queryClient]); |
| 291 | 292 | ||
| 292 | const gitStatusForActions = isGitStatusOutOfSync ? null : gitStatus; | 293 | const gitStatusForActions = isGitStatusOutOfSync ? null : gitStatus; |
| 293 | 294 | ||
| ... | @@ -814,7 +815,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions | ... | @@ -814,7 +815,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 814 | <GroupSeparator className="hidden @3xl/header-actions:block" /> | 815 | <GroupSeparator className="hidden @3xl/header-actions:block" /> |
| 815 | <Menu | 816 | <Menu |
| 816 | onOpenChange={(open) => { | 817 | onOpenChange={(open) => { |
| 817 | if (open) void invalidateGitQueries(queryClient); | 818 | if (open) void invalidateGitStatusQuery(queryClient, gitCwd); |
| 818 | }} | 819 | }} |
| 819 | > | 820 | > |
| 820 | <MenuTrigger | 821 | <MenuTrigger |
apps/web/src/lib/gitReactQuery.test.ts+63-1| ... | @@ -1,10 +1,24 @@ | ... | @@ -1,10 +1,24 @@ |
| 1 | import { QueryClient } from "@tanstack/react-query"; | 1 | import { QueryClient } from "@tanstack/react-query"; |
| 2 | import { describe, expect, it } from "vitest"; | 2 | import { describe, expect, it, vi } from "vitest"; |
| 3 | |||
| 4 | vi.mock("../nativeApi", () => ({ | ||
| 5 | ensureNativeApi: vi.fn(), | ||
| 6 | })); | ||
| 7 | |||
| 8 | vi.mock("../wsRpcClient", () => ({ | ||
| 9 | getWsRpcClient: vi.fn(), | ||
| 10 | })); | ||
| 11 | |||
| 3 | import { | 12 | import { |
| 13 | gitBranchesQueryOptions, | ||
| 4 | gitMutationKeys, | 14 | gitMutationKeys, |
| 15 | gitQueryKeys, | ||
| 5 | gitPreparePullRequestThreadMutationOptions, | 16 | gitPreparePullRequestThreadMutationOptions, |
| 6 | gitPullMutationOptions, | 17 | gitPullMutationOptions, |
| 7 | gitRunStackedActionMutationOptions, | 18 | gitRunStackedActionMutationOptions, |
| 19 | invalidateGitStatusQuery, | ||
| 20 | gitStatusQueryOptions, | ||
| 21 | invalidateGitQueries, | ||
| 8 | } from "./gitReactQuery"; | 22 | } from "./gitReactQuery"; |
| 9 | 23 | ||
| 10 | describe("gitMutationKeys", () => { | 24 | describe("gitMutationKeys", () => { |
| ... | @@ -49,3 +63,51 @@ describe("git mutation options", () => { | ... | @@ -49,3 +63,51 @@ describe("git mutation options", () => { |
| 49 | expect(options.mutationKey).toEqual(gitMutationKeys.preparePullRequestThread("/repo/a")); | 63 | expect(options.mutationKey).toEqual(gitMutationKeys.preparePullRequestThread("/repo/a")); |
| 50 | }); | 64 | }); |
| 51 | }); | 65 | }); |
| 66 | |||
| 67 | describe("invalidateGitQueries", () => { | ||
| 68 | it("can invalidate a single cwd without blasting other git query scopes", async () => { | ||
| 69 | const queryClient = new QueryClient(); | ||
| 70 | |||
| 71 | queryClient.setQueryData(gitQueryKeys.status("/repo/a"), { ok: "a" }); | ||
| 72 | queryClient.setQueryData(gitQueryKeys.branches("/repo/a"), { ok: "a-branches" }); | ||
| 73 | queryClient.setQueryData(gitQueryKeys.status("/repo/b"), { ok: "b" }); | ||
| 74 | queryClient.setQueryData(gitQueryKeys.branches("/repo/b"), { ok: "b-branches" }); | ||
| 75 | |||
| 76 | await invalidateGitQueries(queryClient, { cwd: "/repo/a" }); | ||
| 77 | |||
| 78 | expect( | ||
| 79 | queryClient.getQueryState(gitStatusQueryOptions("/repo/a").queryKey)?.isInvalidated, | ||
| 80 | ).toBe(true); | ||
| 81 | expect( | ||
| 82 | queryClient.getQueryState(gitBranchesQueryOptions("/repo/a").queryKey)?.isInvalidated, | ||
| 83 | ).toBe(true); | ||
| 84 | expect( | ||
| 85 | queryClient.getQueryState(gitStatusQueryOptions("/repo/b").queryKey)?.isInvalidated, | ||
| 86 | ).toBe(false); | ||
| 87 | expect( | ||
| 88 | queryClient.getQueryState(gitBranchesQueryOptions("/repo/b").queryKey)?.isInvalidated, | ||
| 89 | ).toBe(false); | ||
| 90 | }); | ||
| 91 | }); | ||
| 92 | |||
| 93 | describe("invalidateGitStatusQuery", () => { | ||
| 94 | it("invalidates only status for the selected cwd", async () => { | ||
| 95 | const queryClient = new QueryClient(); | ||
| 96 | |||
| 97 | queryClient.setQueryData(gitQueryKeys.status("/repo/a"), { ok: "a" }); | ||
| 98 | queryClient.setQueryData(gitQueryKeys.branches("/repo/a"), { ok: "a-branches" }); | ||
| 99 | queryClient.setQueryData(gitQueryKeys.status("/repo/b"), { ok: "b" }); | ||
| 100 | |||
| 101 | await invalidateGitStatusQuery(queryClient, "/repo/a"); | ||
| 102 | |||
| 103 | expect( | ||
| 104 | queryClient.getQueryState(gitStatusQueryOptions("/repo/a").queryKey)?.isInvalidated, | ||
| 105 | ).toBe(true); | ||
| 106 | expect( | ||
| 107 | queryClient.getQueryState(gitBranchesQueryOptions("/repo/a").queryKey)?.isInvalidated, | ||
| 108 | ).toBe(false); | ||
| 109 | expect( | ||
| 110 | queryClient.getQueryState(gitStatusQueryOptions("/repo/b").queryKey)?.isInvalidated, | ||
| 111 | ).toBe(false); | ||
| 112 | }); | ||
| 113 | }); |
apps/web/src/lib/gitReactQuery.ts+17-1| ... | @@ -23,10 +23,26 @@ export const gitMutationKeys = { | ... | @@ -23,10 +23,26 @@ export const gitMutationKeys = { |
| 23 | ["git", "mutation", "prepare-pull-request-thread", cwd] as const, | 23 | ["git", "mutation", "prepare-pull-request-thread", cwd] as const, |
| 24 | }; | 24 | }; |
| 25 | 25 | ||
| 26 | export function invalidateGitQueries(queryClient: QueryClient) { | 26 | export function invalidateGitQueries(queryClient: QueryClient, input?: { cwd?: string | null }) { |
| 27 | const cwd = input?.cwd ?? null; | ||
| 28 | if (cwd !== null) { | ||
| 29 | return Promise.all([ | ||
| 30 | queryClient.invalidateQueries({ queryKey: gitQueryKeys.status(cwd) }), | ||
| 31 | queryClient.invalidateQueries({ queryKey: gitQueryKeys.branches(cwd) }), | ||
| 32 | ]); | ||
| 33 | } | ||
| 34 | |||
| 27 | return queryClient.invalidateQueries({ queryKey: gitQueryKeys.all }); | 35 | return queryClient.invalidateQueries({ queryKey: gitQueryKeys.all }); |
| 28 | } | 36 | } |
| 29 | 37 | ||
| 38 | export function invalidateGitStatusQuery(queryClient: QueryClient, cwd: string | null) { | ||
| 39 | if (cwd === null) { | ||
| 40 | return Promise.resolve(); | ||
| 41 | } | ||
| 42 | |||
| 43 | return queryClient.invalidateQueries({ queryKey: gitQueryKeys.status(cwd) }); | ||
| 44 | } | ||
| 45 | |||
| 30 | export function gitStatusQueryOptions(cwd: string | null) { | 46 | export function gitStatusQueryOptions(cwd: string | null) { |
| 31 | return queryOptions({ | 47 | return queryOptions({ |
| 32 | queryKey: gitQueryKeys.status(cwd), | 48 | queryKey: gitQueryKeys.status(cwd), |