Skip to content

Commit 6dfce59

Browse files
author
GanJiaKouN16
committed
fix: add shouldIgnoreRowClick guard to unprotected tables
Several tables with row-level click navigation were missing the shouldIgnoreRowClick guard, causing clicks on interactive elements (checkboxes, dropdowns, buttons) to accidentally trigger row navigation. Changes: - Consolidate shouldIgnoreRowClick with broader selector list (merges EvaluationRunsTablePOC's extra selectors: [role='button'], [role='menuitem'], [role='checkbox'], .ant-btn, etc.) - Export INTERACTIVE_ROW_SELECTORS constant for reuse - Add guard to ObservabilityTable (traces) - Add guard to SessionsTable - Add guard to PromptsPage - Add guard to TestcasesTableShell - Add guard to EntityTable - Replace partial data-ivt-stop-row-click check in ScenarioListView with full shouldIgnoreRowClick - Update useEntityTableState to use consolidated selectors - Remove duplicate shouldIgnoreRowClick from navigationActions.ts - Update EvaluationRunsTablePOC to import from shared utility Closes #3254
1 parent d7f2378 commit 6dfce59

11 files changed

Lines changed: 86 additions & 66 deletions

File tree

web/oss/src/components/EvaluationRunsTablePOC/actions/navigationActions.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import type {MouseEvent} from "react"
2-
31
import {message} from "@agenta/ui/app-message"
42
import {getDefaultStore} from "jotai"
53
import Router from "next/router"
@@ -23,14 +21,6 @@ const getUrlState = (): URLState => store.get(urlAtom) as URLState
2321

2422
const getActiveAppId = (): string | null => store.get(routerAppIdAtom)
2523

26-
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>) => {
27-
const target = event.target as HTMLElement | null
28-
if (!target) return false
29-
const interactiveSelector =
30-
"button, a, input, textarea, select, [role='button'], [role='menuitem'], [role='checkbox'], .ant-checkbox, .ant-checkbox-input, .ant-checkbox-inner, .ant-checkbox-wrapper, .ant-btn, .ant-select, .ant-dropdown-trigger"
31-
return Boolean(target.closest(interactiveSelector))
32-
}
33-
3424
interface NavigateToRunParams {
3525
record: EvaluationRunTableRow
3626
scope: "app" | "project"

web/oss/src/components/EvaluationRunsTablePOC/components/EvaluationRunsTable/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {activePreviewProjectIdAtom} from "@/oss/components/EvalRunDetails/atoms/
1313
import {clearAllMetricStatsCaches} from "@/oss/components/EvalRunDetails/atoms/runMetrics"
1414
import {
1515
InfiniteVirtualTableFeatureShell,
16+
shouldIgnoreRowClick,
1617
type TableFeaturePagination,
1718
type TableScopeConfig,
1819
} from "@/oss/components/InfiniteVirtualTable"
@@ -34,7 +35,6 @@ import {
3435
} from "@/oss/lib/onboarding"
3536
import {useQueryParamState} from "@/oss/state/appState"
3637

37-
import {shouldIgnoreRowClick} from "../../actions/navigationActions"
3838
import {
3939
evaluationRunsDeleteContextAtom,
4040
evaluationRunsTableFetchEnabledAtom,

web/oss/src/components/InfiniteVirtualTable/hooks/useTableManager.tsx

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,36 @@ import useTableExport from "./useTableExport"
2727
const dummySearchAtom = atom("")
2828

2929
/**
30-
* Helper to detect if a click event should be ignored for row navigation
30+
* Default CSS selectors for interactive elements that should not trigger row navigation.
31+
* Consolidated from all table implementations to ensure consistent click-through behavior.
32+
*/
33+
export const INTERACTIVE_ROW_SELECTORS = [
34+
"button",
35+
"a",
36+
"input",
37+
"textarea",
38+
"select",
39+
"[role='button']",
40+
"[role='menuitem']",
41+
"[role='checkbox']",
42+
"[data-interactive]",
43+
".ant-dropdown-trigger",
44+
".ant-checkbox-wrapper",
45+
".ant-checkbox",
46+
".ant-checkbox-input",
47+
".ant-checkbox-inner",
48+
".ant-btn",
49+
".ant-select",
50+
].join(", ")
51+
52+
/**
53+
* Helper to detect if a click event should be ignored for row navigation.
3154
* Returns true if the click was on an interactive element (button, link, dropdown, etc.)
3255
*/
3356
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
3457
const target = event.target as HTMLElement
35-
36-
// Check if clicking on interactive elements
37-
if (
38-
target.closest("button") ||
39-
target.closest("a") ||
40-
target.closest(".ant-dropdown-trigger") ||
41-
target.closest(".ant-checkbox-wrapper") ||
42-
target.closest(".ant-select") ||
43-
target.closest("input") ||
44-
target.closest("textarea")
45-
) {
46-
return true
47-
}
48-
49-
return false
58+
if (!target) return false
59+
return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
5060
}
5161

5262
/** Configuration for built-in search. When provided, the hook manages search state internally. */

web/oss/src/components/TestcasesTableNew/components/TestcasesTableShell.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import {useCallback, useMemo, useState} from "react"
1+
import React, {useCallback, useMemo, useState} from "react"
22

33
import {
44
ColumnVisibilityMenuTrigger,
55
defaultHeaderVariant,
66
detectColumnTypes,
77
InfiniteVirtualTableFeatureShell,
8+
shouldIgnoreRowClick,
89
type TableScopeConfig,
910
type TypeChipConfig,
1011
useTypeChipFeature,
@@ -758,7 +759,10 @@ export function TestcasesTableShell(props: TestcasesTableShellProps) {
758759
size: "small" as const,
759760
bordered: true,
760761
onRow: (record: TestcaseTableRow) => ({
761-
onClick: () => onRowClick(record),
762+
onClick: (event: React.MouseEvent) => {
763+
if (shouldIgnoreRowClick(event)) return
764+
onRowClick(record)
765+
},
762766
className: "cursor-pointer hover:bg-gray-50",
763767
}),
764768
}),

web/oss/src/components/pages/observability/components/ObservabilityTable/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {type Key, type ReactNode, useCallback, useEffect, useMemo, useState} from "react"
22

3-
import {InfiniteVirtualTableFeatureShell} from "@agenta/ui/table"
3+
import {InfiniteVirtualTableFeatureShell, shouldIgnoreRowClick} from "@agenta/ui/table"
44
import type {TableFeaturePagination, TableScopeConfig} from "@agenta/ui/table"
55
import {useAtomValue, useSetAtom, useStore} from "jotai"
66
import dynamic from "next/dynamic"
@@ -307,7 +307,10 @@ const ObservabilityTable = () => {
307307
sticky: true,
308308
style: {cursor: "pointer"},
309309
onRow: (record, index) => ({
310-
onClick: () => handleTraceRowClick(record),
310+
onClick: (event) => {
311+
if (shouldIgnoreRowClick(event)) return
312+
handleTraceRowClick(record)
313+
},
311314
"data-tour": index === 0 ? "trace-row" : undefined,
312315
}),
313316
}}

web/oss/src/components/pages/observability/components/SessionsTable/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useCallback, useEffect, useMemo, useState} from "react"
22

3-
import {InfiniteVirtualTableFeatureShell} from "@agenta/ui/table"
3+
import {InfiniteVirtualTableFeatureShell, shouldIgnoreRowClick} from "@agenta/ui/table"
44
import type {TableFeaturePagination, TableScopeConfig} from "@agenta/ui/table"
55
import {useAtomValue, useSetAtom} from "jotai"
66
import dynamic from "next/dynamic"
@@ -141,7 +141,10 @@ const SessionsTable: React.FC = () => {
141141
bordered: true,
142142
loading: isLoading && sessionIds.length === 0,
143143
onRow: (record) => ({
144-
onClick: () => openDrawer({sessionId: record.session_id}),
144+
onClick: (event) => {
145+
if (shouldIgnoreRowClick(event)) return
146+
openDrawer({sessionId: record.session_id})
147+
},
145148
style: {cursor: "pointer"},
146149
}),
147150
}}

web/oss/src/components/pages/prompts/PromptsPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
TableFeaturePagination,
1313
TableScopeConfig,
1414
} from "@agenta/ui/table"
15+
import {shouldIgnoreRowClick} from "@agenta/ui/table"
1516
import {message} from "antd"
1617
import type {TableProps} from "antd/es/table"
1718
import {useAtomValue, useSetAtom} from "jotai"
@@ -686,7 +687,10 @@ const PromptsPage = () => {
686687
scroll: {x: "max-content" as const},
687688
expandable: tableExpandableConfig,
688689
onRow: (record: PromptsTableRow) => ({
689-
onClick: () => handleRowClick(record),
690+
onClick: (event: React.MouseEvent) => {
691+
if (shouldIgnoreRowClick(event)) return
692+
handleRowClick(record)
693+
},
690694
className: "cursor-pointer",
691695
draggable: true,
692696
onDragStart: (event: any) => {

web/packages/agenta-annotation-ui/src/components/AnnotationSession/ScenarioListView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
EXPORT_RESOLVE_SKIP,
3939
InfiniteVirtualTableFeatureShell,
4040
createActionsColumn,
41+
shouldIgnoreRowClick,
4142
type InfiniteVirtualTableRowSelection,
4243
type TableScopeConfig,
4344
type TableExportColumnContext,
@@ -1674,8 +1675,7 @@ const ScenarioListView = memo(function ScenarioListView({
16741675

16751676
// Row click opens annotation drawer
16761677
const handleRowClick = useCallback((_event: React.MouseEvent, record: ScenarioTableRow) => {
1677-
const target = _event.target as HTMLElement
1678-
if (target?.closest("[data-ivt-stop-row-click]")) return
1678+
if (shouldIgnoreRowClick(_event)) return
16791679
setDrawerScenarioId(record.scenarioId)
16801680
}, [])
16811681

web/packages/agenta-entity-ui/src/shared/EntityTable.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {bgColors, cn} from "@agenta/ui/styles"
5252
import {
5353
buildEntityColumns,
5454
InfiniteVirtualTableFeatureShell,
55+
shouldIgnoreRowClick,
5556
type BuildEntityColumnsOptions,
5657
type RowHeightFeatureConfig,
5758
type TableScopeConfig,
@@ -546,8 +547,10 @@ export function EntityTable<
546547
bordered: true,
547548
onRow: selectable
548549
? (record) => ({
549-
onClick: () =>
550-
handleRowSelect(record.id, !selectedIdsSet.has(record.id)),
550+
onClick: (event) => {
551+
if (shouldIgnoreRowClick(event)) return
552+
handleRowSelect(record.id, !selectedIdsSet.has(record.id))
553+
},
551554
className: cn(
552555
"cursor-pointer",
553556
selectedIdsSet.has(record.id) && bgColors.subtle,

web/packages/agenta-ui/src/InfiniteVirtualTable/hooks/useEntityTableState.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ import type {
7272
} from "../paginated/createPaginatedEntityStore"
7373
import type {InfiniteTableRowBase} from "../types"
7474

75+
import {INTERACTIVE_ROW_SELECTORS} from "./useTableManager"
76+
7577
// ============================================================================
7678
// TYPES
7779
// ============================================================================
@@ -182,19 +184,10 @@ export interface UseEntityTableStateResult<TRow extends InfiniteTableRowBase> {
182184
// ============================================================================
183185

184186
/**
185-
* Default selectors for interactive elements that should not trigger row click
187+
* Default selectors for interactive elements that should not trigger row click.
188+
* Uses the consolidated selector string from useTableManager for consistency.
186189
*/
187-
const DEFAULT_INTERACTIVE_SELECTORS = [
188-
"button",
189-
"a",
190-
".ant-dropdown-trigger",
191-
".ant-checkbox-wrapper",
192-
".ant-select",
193-
"input",
194-
"textarea",
195-
"[role='button']",
196-
"[data-interactive]",
197-
]
190+
const DEFAULT_INTERACTIVE_SELECTORS = INTERACTIVE_ROW_SELECTORS.split(", ")
198191

199192
// ============================================================================
200193
// HOOK

0 commit comments

Comments
 (0)