-
Notifications
You must be signed in to change notification settings - Fork 26
fix(lineage): detach run results from focus #1495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,11 @@ if (typeof Object.groupBy === "undefined") { | |
| } | ||
|
|
||
| import type { LineageGraph, LineageGraphNode } from "@datarecce/ui"; | ||
| import type { ColumnLineageData, ServerInfoResult } from "@datarecce/ui/api"; | ||
| import type { | ||
| ColumnLineageData, | ||
| Run, | ||
| ServerInfoResult, | ||
| } from "@datarecce/ui/api"; | ||
| import { | ||
| act, | ||
| fireEvent, | ||
|
|
@@ -193,19 +197,9 @@ vi.mock("@xyflow/react", () => ({ | |
| // Mock @datarecce/ui contexts | ||
| const mockRefetchLineageGraph = vi.fn(); | ||
| const mockRefetchRunsAggregated = vi.fn(); | ||
| let mockActiveRun: | ||
| | { | ||
| type: "schema_diff" | "profile_diff"; | ||
| run_id: string; | ||
| run_at: string; | ||
| params?: { | ||
| model: string; | ||
| }; | ||
| } | ||
| | undefined; | ||
| let mockActiveRun: Run | undefined; | ||
| let mockActiveRunId: string | undefined; | ||
| let mockIsRunResultOpen = false; | ||
| let mockIsProfileDiffRun = false; | ||
|
|
||
| const mockLineageGraphContext = { | ||
| lineageGraph: undefined as LineageGraph | undefined, | ||
|
|
@@ -326,11 +320,6 @@ vi.mock("@datarecce/ui/api", () => ({ | |
| select: vi.fn().mockResolvedValue({ nodes: [] }), | ||
| createLineageDiffCheck: vi.fn().mockResolvedValue({ check_id: "test-check" }), | ||
| createSchemaDiffCheck: vi.fn().mockResolvedValue({ check_id: "test-check" }), | ||
| isHistogramDiffRun: vi.fn(() => false), | ||
| isProfileDiffRun: vi.fn(() => mockIsProfileDiffRun), | ||
| isTopKDiffRun: vi.fn(() => false), | ||
| isValueDiffDetailRun: vi.fn(() => false), | ||
| isValueDiffRun: vi.fn(() => false), | ||
| })); | ||
|
|
||
| // Mock @datarecce/ui/components/lineage | ||
|
|
@@ -883,16 +872,41 @@ function setupWithLineageGraph(lineageGraph?: LineageGraph) { | |
| }); | ||
| } | ||
|
|
||
| function setupOpenProfileRun(model: string) { | ||
| mockActiveRunId = `profile-${model}`; | ||
| type ModelScopedRunType = | ||
| | "profile" | ||
| | "profile_diff" | ||
| | "top_k_diff" | ||
| | "histogram_diff" | ||
| | "value_diff" | ||
| | "value_diff_detail"; | ||
|
|
||
| function setupOpenModelRun(type: ModelScopedRunType, model: string) { | ||
| mockActiveRunId = `${type}-${model}`; | ||
| mockIsRunResultOpen = true; | ||
| mockIsProfileDiffRun = true; | ||
| mockActiveRun = { | ||
| type: "profile_diff", | ||
| type, | ||
| run_id: mockActiveRunId, | ||
| run_at: "2026-07-28T00:00:00Z", | ||
| params: { model }, | ||
| }; | ||
| } as Run; | ||
| } | ||
|
|
||
| function setupOpenProfileRun(model: string) { | ||
| setupOpenModelRun("profile_diff", model); | ||
| } | ||
|
|
||
| function setupOpenRowCountRun( | ||
| type: "row_count" | "row_count_diff", | ||
| model: string, | ||
| ) { | ||
| mockActiveRunId = `${type}-${model}`; | ||
| mockIsRunResultOpen = true; | ||
| mockActiveRun = { | ||
| type, | ||
| run_id: mockActiveRunId, | ||
| run_at: "2026-07-28T00:00:00Z", | ||
| params: { node_names: [model] }, | ||
| } as Run; | ||
| } | ||
|
|
||
| // ============================================================================ | ||
|
|
@@ -937,7 +951,6 @@ describe("LineageView Component", () => { | |
| mockActiveRun = undefined; | ||
| mockActiveRunId = undefined; | ||
| mockIsRunResultOpen = false; | ||
| mockIsProfileDiffRun = false; | ||
|
|
||
| // Reset node state mock | ||
| mockUseNodesStateReturnValue = [[], vi.fn(), vi.fn()]; | ||
|
|
@@ -1783,6 +1796,94 @@ describe("LineageView Component", () => { | |
| }); | ||
| }); | ||
|
|
||
| describe("run focus synchronization", () => { | ||
| it.each([ | ||
| "profile", | ||
| "profile_diff", | ||
| "top_k_diff", | ||
| "histogram_diff", | ||
| "value_diff", | ||
| "value_diff_detail", | ||
| ] as const)( | ||
| "detaches an open %s result when the user focuses another node", | ||
| async (runType) => { | ||
| const lineageGraph = createMockLineageGraph(); | ||
| setupWithLineageGraph(lineageGraph); | ||
| setupOpenModelRun(runType, "node1"); | ||
|
|
||
| render( | ||
| <TestWrapper> | ||
| <TestablePrivateLineageView interactive={true} ref={null} /> | ||
| </TestWrapper>, | ||
| ); | ||
|
|
||
| await waitFor(() => | ||
| expect(screen.getByTestId("node-view")).toHaveAttribute( | ||
| "data-node-id", | ||
| "model.test.node1", | ||
| ), | ||
| ); | ||
|
|
||
| fireEvent.click(screen.getByTestId("click-model.test.node2")); | ||
| await act(async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
| }); | ||
|
|
||
| expect(screen.getByTestId("node-view")).toHaveAttribute( | ||
| "data-node-id", | ||
| "model.test.node2", | ||
| ); | ||
| expect(mockCloseRunResult).not.toHaveBeenCalled(); | ||
| }, | ||
| ); | ||
|
|
||
| it.each(["row_count", "row_count_diff"] as const)( | ||
| "keeps node navigation independent of an open %s result", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two row-count cases do not actually exercise Verified by mutation: adding So the PR description's claim — "treating row-count results as node-bound makes both row-count cases fail" — holds for the unit test but not for these two. Worth rewording the description, and possibly renaming this block to say what it verifies: that the early type-exclusion list keeps navigation independent. The tests themselves are fine and worth keeping. |
||
| async (runType) => { | ||
| const lineageGraph = createMockLineageGraph(); | ||
| setupWithLineageGraph(lineageGraph); | ||
| const view = () => ( | ||
| <TestWrapper> | ||
| <TestablePrivateLineageView interactive={true} ref={null} /> | ||
| </TestWrapper> | ||
| ); | ||
| const { rerender } = render(view()); | ||
|
|
||
| await waitFor(() => | ||
| expect( | ||
| screen.getByTestId("click-model.test.node1"), | ||
| ).toBeInTheDocument(), | ||
| ); | ||
| fireEvent.click(screen.getByTestId("click-model.test.node1")); | ||
| expect(screen.getByTestId("node-view")).toHaveAttribute( | ||
| "data-node-id", | ||
| "model.test.node1", | ||
| ); | ||
|
|
||
| setupOpenRowCountRun(runType, "node1"); | ||
| rerender(view()); | ||
| await act(async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
| }); | ||
| expect(screen.getByTestId("node-view")).toHaveAttribute( | ||
| "data-node-id", | ||
| "model.test.node1", | ||
| ); | ||
|
|
||
| fireEvent.click(screen.getByTestId("click-model.test.node2")); | ||
| await act(async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
| }); | ||
|
|
||
| expect(screen.getByTestId("node-view")).toHaveAttribute( | ||
| "data-node-id", | ||
| "model.test.node2", | ||
| ); | ||
| expect(mockCloseRunResult).not.toHaveBeenCalled(); | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| describe("async layout ownership", () => { | ||
| it("invalidates a deferred CLL request on unmount before it patches or lays out", async () => { | ||
| const lineageGraph = createMockLineageGraph(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scoping note, not a change request.
NodeViewOssis stubbed to render onlydata-node-id, so these assertions cover "focus moved and the run was not force-closed" — symptom (a). They are a proxy for symptom (b), "the profile diff pane empties": if a regression re-broke rendering inside the realNodeViewOss/ProfileDiffResultViewwhile leavingcloseRunResultuncalled, this suite would stay green.A real content-persistence assertion would need heavier setup than this suite does anywhere else, so I would not add it here. Flagging it so the coverage is not read as stronger than it is.