diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx
index c4b232c8dac..0a283a401a2 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx
@@ -8,7 +8,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/emcn'
-import { Copy, Eye, ListFilter, SquareArrowUpRight, X } from '@/components/emcn/icons'
+import { Copy, Eye, Link, ListFilter, SquareArrowUpRight, X } from '@/components/emcn/icons'
import type { WorkflowLog } from '@/stores/logs/filters/types'
interface LogRowContextMenuProps {
@@ -17,6 +17,7 @@ interface LogRowContextMenuProps {
onClose: () => void
log: WorkflowLog | null
onCopyExecutionId: () => void
+ onCopyLink: () => void
onOpenWorkflow: () => void
onOpenPreview: () => void
onToggleWorkflowFilter: () => void
@@ -35,6 +36,7 @@ export const LogRowContextMenu = memo(function LogRowContextMenu({
onClose,
log,
onCopyExecutionId,
+ onCopyLink,
onOpenWorkflow,
onOpenPreview,
onToggleWorkflowFilter,
@@ -71,6 +73,10 @@ export const LogRowContextMenu = memo(function LogRowContextMenu({
Copy Execution ID
+
+
+ Copy Link
+
diff --git a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
index 85de3bebd0e..098f23158a8 100644
--- a/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/logs/logs.tsx
@@ -266,16 +266,17 @@ export default function Logs() {
isSidebarOpen: false,
})
const isInitialized = useRef(false)
+ const pendingExecutionIdRef = useRef(null)
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearchQuery = useDebounce(searchQuery, 300)
useEffect(() => {
- const urlSearch = new URLSearchParams(window.location.search).get('search') || ''
- if (urlSearch && urlSearch !== searchQuery) {
- setSearchQuery(urlSearch)
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ const params = new URLSearchParams(window.location.search)
+ const urlSearch = params.get('search')
+ if (urlSearch) setSearchQuery(urlSearch)
+ const urlExecutionId = params.get('executionId')
+ if (urlExecutionId) pendingExecutionIdRef.current = urlExecutionId
}, [])
const isLive = true
@@ -298,7 +299,6 @@ export default function Logs() {
const [contextMenuOpen, setContextMenuOpen] = useState(false)
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 })
const [contextMenuLog, setContextMenuLog] = useState(null)
- const contextMenuRef = useRef(null)
const [isPreviewOpen, setIsPreviewOpen] = useState(false)
const [previewLogId, setPreviewLogId] = useState(null)
@@ -417,28 +417,30 @@ export default function Logs() {
useFolders(workspaceId)
+ logsRef.current = sortedLogs
+ selectedLogIndexRef.current = selectedLogIndex
+ selectedLogIdRef.current = selectedLogId
+ logsRefetchRef.current = logsQuery.refetch
+ activeLogRefetchRef.current = activeLogQuery.refetch
+ logsQueryRef.current = {
+ isFetching: logsQuery.isFetching,
+ hasNextPage: logsQuery.hasNextPage ?? false,
+ fetchNextPage: logsQuery.fetchNextPage,
+ }
+
useEffect(() => {
- logsRef.current = sortedLogs
- }, [sortedLogs])
- useEffect(() => {
- selectedLogIndexRef.current = selectedLogIndex
- }, [selectedLogIndex])
- useEffect(() => {
- selectedLogIdRef.current = selectedLogId
- }, [selectedLogId])
- useEffect(() => {
- logsRefetchRef.current = logsQuery.refetch
- }, [logsQuery.refetch])
- useEffect(() => {
- activeLogRefetchRef.current = activeLogQuery.refetch
- }, [activeLogQuery.refetch])
- useEffect(() => {
- logsQueryRef.current = {
- isFetching: logsQuery.isFetching,
- hasNextPage: logsQuery.hasNextPage ?? false,
- fetchNextPage: logsQuery.fetchNextPage,
+ if (!pendingExecutionIdRef.current) return
+ const targetExecutionId = pendingExecutionIdRef.current
+ const found = sortedLogs.find((l) => l.executionId === targetExecutionId)
+ if (found) {
+ pendingExecutionIdRef.current = null
+ dispatch({ type: 'TOGGLE_LOG', logId: found.id })
+ } else if (!logsQuery.hasNextPage && logsQuery.status === 'success') {
+ pendingExecutionIdRef.current = null
+ } else if (!logsQuery.isFetching && logsQuery.status === 'success') {
+ logsQueryRef.current.fetchNextPage()
}
- }, [logsQuery.isFetching, logsQuery.hasNextPage, logsQuery.fetchNextPage])
+ }, [sortedLogs, logsQuery.hasNextPage, logsQuery.isFetching, logsQuery.status])
useEffect(() => {
const timers = refreshTimersRef.current
@@ -490,10 +492,17 @@ export default function Logs() {
const handleCopyExecutionId = useCallback(() => {
if (contextMenuLog?.executionId) {
- navigator.clipboard.writeText(contextMenuLog.executionId)
+ navigator.clipboard.writeText(contextMenuLog.executionId).catch(() => {})
}
}, [contextMenuLog])
+ const handleCopyLink = useCallback(() => {
+ if (contextMenuLog?.executionId) {
+ const url = `${window.location.origin}/workspace/${workspaceId}/logs?executionId=${contextMenuLog.executionId}`
+ navigator.clipboard.writeText(url).catch(() => {})
+ }
+ }, [contextMenuLog, workspaceId])
+
const handleOpenWorkflow = useCallback(() => {
const wfId = contextMenuLog?.workflow?.id || contextMenuLog?.workflowId
if (wfId) {
@@ -1165,6 +1174,7 @@ export default function Logs() {
onClose={handleCloseContextMenu}
log={contextMenuLog}
onCopyExecutionId={handleCopyExecutionId}
+ onCopyLink={handleCopyLink}
onOpenWorkflow={handleOpenWorkflow}
onOpenPreview={handleOpenPreview}
onToggleWorkflowFilter={handleToggleWorkflowFilter}
diff --git a/apps/sim/components/emcn/icons/index.ts b/apps/sim/components/emcn/icons/index.ts
index 0e97f5bce57..bf109bf5ba0 100644
--- a/apps/sim/components/emcn/icons/index.ts
+++ b/apps/sim/components/emcn/icons/index.ts
@@ -42,6 +42,7 @@ export { Key } from './key'
export { KeySquare } from './key-square'
export { Layout } from './layout'
export { Library } from './library'
+export { Link } from './link'
export { ListFilter } from './list-filter'
export { Loader } from './loader'
export { Lock } from './lock'
diff --git a/apps/sim/components/emcn/icons/link.tsx b/apps/sim/components/emcn/icons/link.tsx
new file mode 100644
index 00000000000..46ae6a130ea
--- /dev/null
+++ b/apps/sim/components/emcn/icons/link.tsx
@@ -0,0 +1,26 @@
+import type { SVGProps } from 'react'
+
+/**
+ * Link icon component
+ * @param props - SVG properties including className, size, etc.
+ */
+export function Link(props: SVGProps) {
+ return (
+
+ )
+}