From 305635fb6b78116dd7b963af32c4929fe33a3f01 Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Thu, 23 Jul 2026 14:53:32 +0100 Subject: [PATCH 1/2] fix(tasks): refresh active cloud run on task open Generated-By: PostHog Code Task-Id: 048d944e-5259-4824-8a45-87a9fa88c148 --- .../hooks/useRefreshedTask.test.tsx | 57 +++++++++++++++++++ .../task-detail/hooks/useRefreshedTask.ts | 13 +++++ .../features/task-detail/hooks/useTaskData.ts | 6 +- 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 packages/ui/src/features/task-detail/hooks/useRefreshedTask.test.tsx create mode 100644 packages/ui/src/features/task-detail/hooks/useRefreshedTask.ts diff --git a/packages/ui/src/features/task-detail/hooks/useRefreshedTask.test.tsx b/packages/ui/src/features/task-detail/hooks/useRefreshedTask.test.tsx new file mode 100644 index 0000000000..9d11d1c35a --- /dev/null +++ b/packages/ui/src/features/task-detail/hooks/useRefreshedTask.test.tsx @@ -0,0 +1,57 @@ +import type { Task } from "@posthog/shared/domain-types"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import type { PropsWithChildren } from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { useRefreshedTask } from "./useRefreshedTask"; + +const mocks = vi.hoisted(() => ({ getTask: vi.fn() })); + +vi.mock("@posthog/ui/features/auth/authClientImperative", () => ({ + getAuthenticatedClient: vi.fn(async () => ({ getTask: mocks.getTask })), +})); + +function task(runId: string, status: "failed" | "in_progress"): Task { + return { + id: "task-123", + title: "Cloud task", + description: "Keep working", + repository: null, + latest_run: { + id: runId, + task: "task-123", + environment: "cloud", + status, + state: {}, + }, + } as Task; +} + +describe("useRefreshedTask", () => { + beforeEach(() => { + mocks.getTask.mockReset(); + }); + + it("replaces a cached failed run with the authoritative resumed run", async () => { + const failedParent = task("run-parent", "failed"); + const resumedChild = task("run-child", "in_progress"); + mocks.getTask.mockResolvedValue(resumedChild); + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + const wrapper = ({ children }: PropsWithChildren) => ( + {children} + ); + + const { result } = renderHook( + () => useRefreshedTask("task-123", failedParent), + { wrapper }, + ); + + expect(result.current.latest_run?.id).toBe("run-parent"); + await waitFor(() => { + expect(result.current.latest_run?.id).toBe("run-child"); + }); + expect(mocks.getTask).toHaveBeenCalledWith("task-123"); + }); +}); diff --git a/packages/ui/src/features/task-detail/hooks/useRefreshedTask.ts b/packages/ui/src/features/task-detail/hooks/useRefreshedTask.ts new file mode 100644 index 0000000000..e2f6be477d --- /dev/null +++ b/packages/ui/src/features/task-detail/hooks/useRefreshedTask.ts @@ -0,0 +1,13 @@ +import type { Task } from "@posthog/shared/domain-types"; +import { useQuery } from "@tanstack/react-query"; +import { taskDetailQuery } from "../../tasks/queries"; + +export function useRefreshedTask(taskId: string, initialTask: Task): Task { + const { data } = useQuery({ + ...taskDetailQuery(taskId), + initialData: initialTask, + refetchOnMount: "always", + }); + + return data; +} diff --git a/packages/ui/src/features/task-detail/hooks/useTaskData.ts b/packages/ui/src/features/task-detail/hooks/useTaskData.ts index ae278ba306..6174eea501 100644 --- a/packages/ui/src/features/task-detail/hooks/useTaskData.ts +++ b/packages/ui/src/features/task-detail/hooks/useTaskData.ts @@ -11,6 +11,7 @@ import { useMemo } from "react"; import { cloneStore } from "../../clone/cloneStore"; import { useTasks } from "../../tasks/useTasks"; import { useWorkspace } from "../../workspace/useWorkspace"; +import { useRefreshedTask } from "./useRefreshedTask"; interface UseTaskDataParams { taskId: string; @@ -20,10 +21,11 @@ interface UseTaskDataParams { export function useTaskData({ taskId, initialTask }: UseTaskDataParams) { const trpcReact = useWorkspaceTRPC(); const { data: tasks = [] } = useTasks(); + const refreshedTask = useRefreshedTask(taskId, initialTask); const task = useMemo( - () => tasks.find((t) => t.id === taskId) || initialTask, - [tasks, taskId, initialTask], + () => refreshedTask ?? tasks.find((t) => t.id === taskId) ?? initialTask, + [refreshedTask, tasks, taskId, initialTask], ); const workspace = useWorkspace(taskId); From 2d0ccbad432e89807769e4084567f8e3ce32e057 Mon Sep 17 00:00:00 2001 From: Alessandro Pogliaghi Date: Thu, 23 Jul 2026 15:20:30 +0100 Subject: [PATCH 2/2] refactor(tasks): use authoritative task detail directly Generated-By: PostHog Code Task-Id: 048d944e-5259-4824-8a45-87a9fa88c148 --- .../ui/src/features/task-detail/hooks/useTaskData.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/ui/src/features/task-detail/hooks/useTaskData.ts b/packages/ui/src/features/task-detail/hooks/useTaskData.ts index 6174eea501..477dd1e704 100644 --- a/packages/ui/src/features/task-detail/hooks/useTaskData.ts +++ b/packages/ui/src/features/task-detail/hooks/useTaskData.ts @@ -7,9 +7,7 @@ import { getTaskRepository } from "@posthog/shared"; import type { Task } from "@posthog/shared/domain-types"; import { useWorkspaceTRPC } from "@posthog/workspace-client/trpc"; import { useQuery } from "@tanstack/react-query"; -import { useMemo } from "react"; import { cloneStore } from "../../clone/cloneStore"; -import { useTasks } from "../../tasks/useTasks"; import { useWorkspace } from "../../workspace/useWorkspace"; import { useRefreshedTask } from "./useRefreshedTask"; @@ -20,13 +18,7 @@ interface UseTaskDataParams { export function useTaskData({ taskId, initialTask }: UseTaskDataParams) { const trpcReact = useWorkspaceTRPC(); - const { data: tasks = [] } = useTasks(); - const refreshedTask = useRefreshedTask(taskId, initialTask); - - const task = useMemo( - () => refreshedTask ?? tasks.find((t) => t.id === taskId) ?? initialTask, - [refreshedTask, tasks, taskId, initialTask], - ); + const task = useRefreshedTask(taskId, initialTask); const workspace = useWorkspace(taskId); const repoPath = workspace?.folderPath ?? null;