Skip to content

Commit eba48e8

Browse files
authored
feat(logs): add cancel execution to log row context menu (#4130)
* feat(logs): add cancel execution to log row context menu * lint * fix(logs): check success response and use targeted cache invalidation
1 parent cd7e413 commit eba48e8

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

apps/sim/app/workspace/[workspaceId]/logs/components/log-row-context-menu/log-row-context-menu.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface LogRowContextMenuProps {
2222
onOpenPreview: () => void
2323
onToggleWorkflowFilter: () => void
2424
onClearAllFilters: () => void
25+
onCancelExecution: () => void
2526
isFilteredByThisWorkflow: boolean
2627
hasActiveFilters: boolean
2728
}
@@ -41,11 +42,13 @@ export const LogRowContextMenu = memo(function LogRowContextMenu({
4142
onOpenPreview,
4243
onToggleWorkflowFilter,
4344
onClearAllFilters,
45+
onCancelExecution,
4446
isFilteredByThisWorkflow,
4547
hasActiveFilters,
4648
}: LogRowContextMenuProps) {
4749
const hasExecutionId = Boolean(log?.executionId)
4850
const hasWorkflow = Boolean(log?.workflow?.id || log?.workflowId)
51+
const isRunning = log?.status === 'running' && hasExecutionId && hasWorkflow
4952

5053
return (
5154
<DropdownMenu open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>
@@ -69,6 +72,15 @@ export const LogRowContextMenu = memo(function LogRowContextMenu({
6972
sideOffset={4}
7073
onCloseAutoFocus={(e) => e.preventDefault()}
7174
>
75+
{isRunning && (
76+
<>
77+
<DropdownMenuItem onSelect={onCancelExecution} className='text-destructive'>
78+
<X />
79+
Cancel Execution
80+
</DropdownMenuItem>
81+
<DropdownMenuSeparator />
82+
</>
83+
)}
7284
<DropdownMenuItem disabled={!hasExecutionId} onSelect={onCopyExecutionId}>
7385
<Copy />
7486
Copy Execution ID

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { getBlock } from '@/blocks/registry'
5454
import { useFolderMap, useFolders } from '@/hooks/queries/folders'
5555
import {
5656
prefetchLogDetail,
57+
useCancelExecution,
5758
useDashboardStats,
5859
useLogDetail,
5960
useLogsList,
@@ -534,6 +535,17 @@ export default function Logs() {
534535
}
535536
}, [contextMenuLog])
536537

538+
const cancelExecution = useCancelExecution()
539+
540+
const handleCancelExecution = useCallback(() => {
541+
const workflowId = contextMenuLog?.workflow?.id || contextMenuLog?.workflowId
542+
const executionId = contextMenuLog?.executionId
543+
if (workflowId && executionId) {
544+
cancelExecution.mutate({ workflowId, executionId })
545+
}
546+
// eslint-disable-next-line react-hooks/exhaustive-deps
547+
}, [contextMenuLog])
548+
537549
const contextMenuWorkflowId = contextMenuLog?.workflow?.id || contextMenuLog?.workflowId
538550
const isFilteredByThisWorkflow = Boolean(
539551
contextMenuWorkflowId && workflowIds.length === 1 && workflowIds[0] === contextMenuWorkflowId
@@ -1178,6 +1190,7 @@ export default function Logs() {
11781190
onCopyLink={handleCopyLink}
11791191
onOpenWorkflow={handleOpenWorkflow}
11801192
onOpenPreview={handleOpenPreview}
1193+
onCancelExecution={handleCancelExecution}
11811194
onToggleWorkflowFilter={handleToggleWorkflowFilter}
11821195
onClearAllFilters={handleClearAllFilters}
11831196
isFilteredByThisWorkflow={isFilteredByThisWorkflow}

apps/sim/hooks/queries/logs.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
keepPreviousData,
33
type QueryClient,
44
useInfiniteQuery,
5+
useMutation,
56
useQuery,
7+
useQueryClient,
68
} from '@tanstack/react-query'
79
import { getEndDateFromTimeRange, getStartDateFromTimeRange } from '@/lib/logs/filters'
810
import { parseQuery, queryToApiParams } from '@/lib/logs/query-parser'
@@ -273,3 +275,29 @@ export function useExecutionSnapshot(executionId: string | undefined) {
273275
staleTime: 5 * 60 * 1000, // 5 minutes - execution snapshots don't change
274276
})
275277
}
278+
279+
export function useCancelExecution() {
280+
const queryClient = useQueryClient()
281+
return useMutation({
282+
mutationFn: async ({
283+
workflowId,
284+
executionId,
285+
}: {
286+
workflowId: string
287+
executionId: string
288+
}) => {
289+
const res = await fetch(`/api/workflows/${workflowId}/executions/${executionId}/cancel`, {
290+
method: 'POST',
291+
})
292+
if (!res.ok) throw new Error('Failed to cancel execution')
293+
const data = await res.json()
294+
if (!data.success) throw new Error('Failed to cancel execution')
295+
return data
296+
},
297+
onSettled: () => {
298+
queryClient.invalidateQueries({ queryKey: logKeys.lists() })
299+
queryClient.invalidateQueries({ queryKey: logKeys.details() })
300+
queryClient.invalidateQueries({ queryKey: [...logKeys.all, 'stats'] })
301+
},
302+
})
303+
}

0 commit comments

Comments
 (0)