Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ export const triggerRunInvocationAtom = atom(
traceId: result.traceId ?? undefined,
status: "failure",
references,
error: {message: errorMessage},
error: {
message: errorMessage,
...(result.error?.stacktrace ? {stacktrace: result.error.stacktrace} : {}),
...(result.error?.type ? {type: result.error.type} : {}),
},
Comment on lines +211 to +215
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Forward result.error.code when persisting invocation failures.

The failure payload currently strips code, so UI/error tooling cannot show provider/HTTP code consistently.

Proposed fix
                     error: {
                         message: errorMessage,
+                        ...(result.error?.code ? {code: result.error.code} : {}),
                         ...(result.error?.stacktrace ? {stacktrace: result.error.stacktrace} : {}),
                         ...(result.error?.type ? {type: result.error.type} : {}),
                     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
error: {
message: errorMessage,
...(result.error?.stacktrace ? {stacktrace: result.error.stacktrace} : {}),
...(result.error?.type ? {type: result.error.type} : {}),
},
error: {
message: errorMessage,
...(result.error?.code ? {code: result.error.code} : {}),
...(result.error?.stacktrace ? {stacktrace: result.error.stacktrace} : {}),
...(result.error?.type ? {type: result.error.type} : {}),
},

})

await updateScenarioStatus(scenarioId, EvaluationStatus.FAILURE)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type {MouseEvent} from "react"

import {message} from "@agenta/ui/app-message"
import {getDefaultStore} from "jotai"
import Router from "next/router"
Expand All @@ -23,14 +21,6 @@ const getUrlState = (): URLState => store.get(urlAtom) as URLState

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

export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>) => {
const target = event.target as HTMLElement | null
if (!target) return false
const interactiveSelector =
"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"
return Boolean(target.closest(interactiveSelector))
}

interface NavigateToRunParams {
record: EvaluationRunTableRow
scope: "app" | "project"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {activePreviewProjectIdAtom} from "@/oss/components/EvalRunDetails/atoms/
import {clearAllMetricStatsCaches} from "@/oss/components/EvalRunDetails/atoms/runMetrics"
import {
InfiniteVirtualTableFeatureShell,
shouldIgnoreRowClick,
type TableFeaturePagination,
type TableScopeConfig,
} from "@/oss/components/InfiniteVirtualTable"
Expand All @@ -34,7 +35,6 @@ import {
} from "@/oss/lib/onboarding"
import {useQueryParamState} from "@/oss/state/appState"

import {shouldIgnoreRowClick} from "../../actions/navigationActions"
import {
evaluationRunsDeleteContextAtom,
evaluationRunsTableFetchEnabledAtom,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,36 @@ import useTableExport from "./useTableExport"
const dummySearchAtom = atom("")

/**
* Helper to detect if a click event should be ignored for row navigation
* Default CSS selectors for interactive elements that should not trigger row navigation.
* Consolidated from all table implementations to ensure consistent click-through behavior.
*/
export const INTERACTIVE_ROW_SELECTORS = [
"button",
"a",
"input",
"textarea",
"select",
"[role='button']",
"[role='menuitem']",
"[role='checkbox']",
"[data-interactive]",
".ant-dropdown-trigger",
".ant-checkbox-wrapper",
".ant-checkbox",
".ant-checkbox-input",
".ant-checkbox-inner",
".ant-btn",
".ant-select",
].join(", ")

/**
* Helper to detect if a click event should be ignored for row navigation.
* Returns true if the click was on an interactive element (button, link, dropdown, etc.)
*/
export const shouldIgnoreRowClick = (event: MouseEvent<HTMLElement>): boolean => {
const target = event.target as HTMLElement

// Check if clicking on interactive elements
if (
target.closest("button") ||
target.closest("a") ||
target.closest(".ant-dropdown-trigger") ||
target.closest(".ant-checkbox-wrapper") ||
target.closest(".ant-select") ||
target.closest("input") ||
target.closest("textarea")
) {
return true
}

return false
if (!target) return false
return Boolean(target.closest(INTERACTIVE_ROW_SELECTORS))
}

/** Configuration for built-in search. When provided, the hook manages search state internally. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {useCallback, useMemo, useState} from "react"
import React, {useCallback, useMemo, useState} from "react"

import {
ColumnVisibilityMenuTrigger,
defaultHeaderVariant,
detectColumnTypes,
InfiniteVirtualTableFeatureShell,
shouldIgnoreRowClick,
type TableScopeConfig,
type TypeChipConfig,
useTypeChipFeature,
Expand Down Expand Up @@ -758,7 +759,10 @@ export function TestcasesTableShell(props: TestcasesTableShellProps) {
size: "small" as const,
bordered: true,
onRow: (record: TestcaseTableRow) => ({
onClick: () => onRowClick(record),
onClick: (event: React.MouseEvent) => {
if (shouldIgnoreRowClick(event)) return
onRowClick(record)
},
className: "cursor-pointer hover:bg-gray-50",
}),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type Key, type ReactNode, useCallback, useEffect, useMemo, useState} from "react"

import {InfiniteVirtualTableFeatureShell} from "@agenta/ui/table"
import {InfiniteVirtualTableFeatureShell, shouldIgnoreRowClick} from "@agenta/ui/table"
import type {TableFeaturePagination, TableScopeConfig} from "@agenta/ui/table"
import {useAtomValue, useSetAtom, useStore} from "jotai"
import dynamic from "next/dynamic"
Expand Down Expand Up @@ -307,7 +307,10 @@ const ObservabilityTable = () => {
sticky: true,
style: {cursor: "pointer"},
onRow: (record, index) => ({
onClick: () => handleTraceRowClick(record),
onClick: (event) => {
if (shouldIgnoreRowClick(event)) return
handleTraceRowClick(record)
},
"data-tour": index === 0 ? "trace-row" : undefined,
}),
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useCallback, useEffect, useMemo, useState} from "react"

import {InfiniteVirtualTableFeatureShell} from "@agenta/ui/table"
import {InfiniteVirtualTableFeatureShell, shouldIgnoreRowClick} from "@agenta/ui/table"
import type {TableFeaturePagination, TableScopeConfig} from "@agenta/ui/table"
import {useAtomValue, useSetAtom} from "jotai"
import dynamic from "next/dynamic"
Expand Down Expand Up @@ -141,7 +141,10 @@ const SessionsTable: React.FC = () => {
bordered: true,
loading: isLoading && sessionIds.length === 0,
onRow: (record) => ({
onClick: () => openDrawer({sessionId: record.session_id}),
onClick: (event) => {
if (shouldIgnoreRowClick(event)) return
openDrawer({sessionId: record.session_id})
},
style: {cursor: "pointer"},
}),
}}
Expand Down
6 changes: 5 additions & 1 deletion web/oss/src/components/pages/prompts/PromptsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
TableFeaturePagination,
TableScopeConfig,
} from "@agenta/ui/table"
import {shouldIgnoreRowClick} from "@agenta/ui/table"
import {message} from "antd"
import type {TableProps} from "antd/es/table"
import {useAtomValue, useSetAtom} from "jotai"
Expand Down Expand Up @@ -686,7 +687,10 @@ const PromptsPage = () => {
scroll: {x: "max-content" as const},
expandable: tableExpandableConfig,
onRow: (record: PromptsTableRow) => ({
onClick: () => handleRowClick(record),
onClick: (event: React.MouseEvent) => {
if (shouldIgnoreRowClick(event)) return
handleRowClick(record)
},
className: "cursor-pointer",
draggable: true,
onDragStart: (event: any) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useState} from "react"
import type {User} from "@agenta/shared/types"
import {message} from "@agenta/ui/app-message"
import {EditOutlined, MoreOutlined, SyncOutlined} from "@ant-design/icons"
import {ArrowClockwise, Trash} from "@phosphor-icons/react"
import {ArrowClockwise, Key, Trash} from "@phosphor-icons/react"
import {Button, Dropdown, Input, Modal, Space, Tag, Tooltip, Typography} from "antd"

import AlertPopup from "@/oss/components/AlertPopup/AlertPopup"
Expand All @@ -12,7 +12,7 @@ import {isEmailInvitationsEnabled} from "@/oss/lib/helpers/isEE"
import {useEntitlements} from "@/oss/lib/helpers/useEntitlements"
import {snakeToTitle} from "@/oss/lib/helpers/utils"
import {WorkspaceMember} from "@/oss/lib/Types"
import {updateUsername} from "@/oss/services/profile"
import {resetPassword, updateUsername} from "@/oss/services/profile"
import {
assignWorkspaceRole,
removeFromWorkspace,
Expand All @@ -23,6 +23,9 @@ import {useOrgData} from "@/oss/state/org"
import {useProfileData} from "@/oss/state/profile"
import {useWorkspaceRoles} from "@/oss/state/workspace"

import GenerateResetLinkModal from "./Modals/GenerateResetLinkModal"
import PasswordResetLinkModal from "./Modals/PasswordResetLinkModal"

export const Actions: React.FC<{
member: WorkspaceMember
hidden?: boolean
Expand All @@ -39,6 +42,10 @@ export const Actions: React.FC<{
const {refetch: refetchProfile} = useProfileData()
const [renameOpen, setRenameOpen] = useState(false)
const [renameValue, setRenameValue] = useState(user.username || "")
const [generateResetLinkOpen, setGenerateResetLinkOpen] = useState(false)
const [resetLinkOpen, setResetLinkOpen] = useState(false)
const [resetLink, setResetLink] = useState("")
const [resetLoading, setResetLoading] = useState(false)

if (hidden && !selfMenu) return null

Expand Down Expand Up @@ -90,6 +97,24 @@ export const Actions: React.FC<{
}
}

const handleResetPassword = async () => {
setResetLoading(true)
try {
const link = await resetPassword(user.id)
setGenerateResetLinkOpen(false)
setResetLink(link)
setResetLinkOpen(true)
} catch (error: any) {
const detail =
error?.response?.data?.detail ||
error?.message ||
"Unable to generate reset password link"
message.error(detail)
} finally {
setResetLoading(false)
}
}

return (
<>
<Dropdown
Expand Down Expand Up @@ -127,6 +152,19 @@ export const Actions: React.FC<{
},
]
: []),
...(isMember
? [
{
key: "reset_password",
label: "Reset password",
icon: <Key size={16} />,
onClick: (e: any) => {
e.domEvent.stopPropagation()
setGenerateResetLinkOpen(true)
},
},
]
: []),
{
key: "remove",
label: "Remove",
Expand Down Expand Up @@ -165,6 +203,21 @@ export const Actions: React.FC<{
placeholder="New username"
/>
</Modal>

<GenerateResetLinkModal
open={generateResetLinkOpen}
username={user.username}
onCancel={() => setGenerateResetLinkOpen(false)}
onOk={handleResetPassword}
confirmLoading={resetLoading}
/>

<PasswordResetLinkModal
open={resetLinkOpen}
username={user.username}
generatedLink={resetLink}
onCancel={() => setResetLinkOpen(false)}
/>
</>
)
}
Expand Down
2 changes: 1 addition & 1 deletion web/oss/src/services/evaluations/invocations/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const upsertStepResultWithInvocation = async ({
status: string
references?: InvocationReferences
outputs?: unknown
error?: {message: string; stacktrace?: string}
error?: {message: string; stacktrace?: string; type?: string}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include error.code in the persistence contract.

This boundary type currently blocks status-code propagation, so HTTP/provider code can be lost before UI rendering.

Proposed fix
-    error?: {message: string; stacktrace?: string; type?: string}
+    error?: {message: string; code?: string; stacktrace?: string; type?: string}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
error?: {message: string; stacktrace?: string; type?: string}
error?: {message: string; code?: string; stacktrace?: string; type?: string}

}): Promise<void> => {
const {projectId} = getProjectValues()

Expand Down
14 changes: 14 additions & 0 deletions web/oss/src/services/profile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,17 @@ export const changePassword = async (payload: {
body: JSON.stringify(payload),
})
}

/**
* Generate a password reset link for a user (admin action).
* Returns the reset password link string.
*/
export const resetPassword = async (userId: string): Promise<string> => {
const base = getBaseUrl()
const url = new URL("api/profile/reset-password", base)
url.searchParams.set("user_id", userId)
const data = await fetchJson<string>(url, {
method: "POST",
})
return data
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
EXPORT_RESOLVE_SKIP,
InfiniteVirtualTableFeatureShell,
createActionsColumn,
shouldIgnoreRowClick,
type InfiniteVirtualTableRowSelection,
type TableScopeConfig,
type TableExportColumnContext,
Expand Down Expand Up @@ -1674,8 +1675,7 @@ const ScenarioListView = memo(function ScenarioListView({

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

Expand Down
2 changes: 2 additions & 0 deletions web/packages/agenta-entities/src/runnable/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export interface ExecutionResult {
error?: {
message: string
code?: string
type?: string
stacktrace?: string
}
trace?: TraceInfo
metrics?: ExecutionMetrics
Expand Down
7 changes: 5 additions & 2 deletions web/packages/agenta-entity-ui/src/shared/EntityTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {bgColors, cn} from "@agenta/ui/styles"
import {
buildEntityColumns,
InfiniteVirtualTableFeatureShell,
shouldIgnoreRowClick,
type BuildEntityColumnsOptions,
type RowHeightFeatureConfig,
type TableScopeConfig,
Expand Down Expand Up @@ -546,8 +547,10 @@ export function EntityTable<
bordered: true,
onRow: selectable
? (record) => ({
onClick: () =>
handleRowSelect(record.id, !selectedIdsSet.has(record.id)),
onClick: (event) => {
if (shouldIgnoreRowClick(event)) return
handleRowSelect(record.id, !selectedIdsSet.has(record.id))
},
className: cn(
"cursor-pointer",
selectedIdsSet.has(record.id) && bgColors.subtle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface ExecuteWorkflowRevisionResult {
structuredOutput?: unknown
traceId?: string | null
spanId?: string | null
error?: {message: string; code?: string}
error?: {message: string; code?: string; type?: string; stacktrace?: string}
}

// ============================================================================
Expand Down
Loading