Skip to content

Commit 8db4430

Browse files
committed
improvement: preivew accuracy, invite-modal admin, logs live
1 parent 6a4deae commit 8db4430

File tree

3 files changed

+48
-87
lines changed

3 files changed

+48
-87
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function Logs() {
7878
// eslint-disable-next-line react-hooks/exhaustive-deps
7979
}, [])
8080

81-
const [isLive, setIsLive] = useState(false)
81+
const [isLive, setIsLive] = useState(true)
8282
const [isVisuallyRefreshing, setIsVisuallyRefreshing] = useState(false)
8383
const [isExporting, setIsExporting] = useState(false)
8484
const isSearchOpenRef = useRef<boolean>(false)

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/preview-workflow.tsx

Lines changed: 46 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@ import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/type
2323

2424
const logger = createLogger('PreviewWorkflow')
2525

26-
/**
27-
* Gets block dimensions for preview purposes.
28-
* For containers, uses stored dimensions or defaults.
29-
* For regular blocks, uses stored height or estimates based on type.
30-
*/
26+
/** Gets block dimensions, using stored values or defaults. */
3127
function getPreviewBlockDimensions(block: BlockState): { width: number; height: number } {
3228
if (block.type === 'loop' || block.type === 'parallel') {
3329
return {
@@ -50,10 +46,7 @@ function getPreviewBlockDimensions(block: BlockState): { width: number; height:
5046
return estimateBlockDimensions(block.type)
5147
}
5248

53-
/**
54-
* Calculates container dimensions based on child block positions and sizes.
55-
* Mirrors the logic from useNodeUtilities.calculateLoopDimensions.
56-
*/
49+
/** Calculates container dimensions from child block positions. */
5750
function calculateContainerDimensions(
5851
containerId: string,
5952
blocks: Record<string, BlockState>
@@ -91,12 +84,7 @@ function calculateContainerDimensions(
9184
return { width, height }
9285
}
9386

94-
/**
95-
* Finds the leftmost block ID from a workflow state.
96-
* Excludes subflow containers (loop/parallel) from consideration.
97-
* @param workflowState - The workflow state to search
98-
* @returns The ID of the leftmost block, or null if no blocks exist
99-
*/
87+
/** Finds the leftmost block ID, excluding subflow containers. */
10088
export function getLeftmostBlockId(workflowState: WorkflowState | null | undefined): string | null {
10189
if (!workflowState?.blocks) return null
10290

@@ -118,7 +106,7 @@ export function getLeftmostBlockId(workflowState: WorkflowState | null | undefin
118106
/** Execution status for edges/nodes in the preview */
119107
type ExecutionStatus = 'success' | 'error' | 'not-executed'
120108

121-
/** Calculates absolute position for blocks, handling nested subflows */
109+
/** Calculates absolute position, handling nested subflows. */
122110
function calculateAbsolutePosition(
123111
block: BlockState,
124112
blocks: Record<string, BlockState>
@@ -164,10 +152,7 @@ interface PreviewWorkflowProps {
164152
lightweight?: boolean
165153
}
166154

167-
/**
168-
* Preview node types using minimal components without hooks or store subscriptions.
169-
* This prevents interaction issues while allowing canvas panning and node clicking.
170-
*/
155+
/** Preview node types using minimal, hook-free components. */
171156
const previewNodeTypes: NodeTypes = {
172157
workflowBlock: PreviewBlock,
173158
noteBlock: PreviewBlock,
@@ -185,11 +170,7 @@ interface FitViewOnChangeProps {
185170
containerRef: React.RefObject<HTMLDivElement | null>
186171
}
187172

188-
/**
189-
* Helper component that calls fitView when the set of nodes changes or when the container resizes.
190-
* Only triggers on actual node additions/removals, not on selection changes.
191-
* Must be rendered inside ReactFlowProvider.
192-
*/
173+
/** Calls fitView on node changes or container resize. */
193174
function FitViewOnChange({ nodeIds, fitPadding, containerRef }: FitViewOnChangeProps) {
194175
const { fitView } = useReactFlow()
195176
const lastNodeIdsRef = useRef<string | null>(null)
@@ -229,16 +210,7 @@ function FitViewOnChange({ nodeIds, fitPadding, containerRef }: FitViewOnChangeP
229210
return null
230211
}
231212

232-
/**
233-
* Readonly workflow component for visualizing workflow state.
234-
* Renders blocks, subflows, and edges with execution status highlighting.
235-
*
236-
* @remarks
237-
* - Supports panning and node click interactions
238-
* - Shows execution path via green edges for successful paths
239-
* - Error edges display red by default, green when error path was taken
240-
* - Fits view automatically when nodes change or container resizes
241-
*/
213+
/** Readonly workflow visualization with execution status highlighting. */
242214
export function PreviewWorkflow({
243215
workflowState,
244216
className,
@@ -300,49 +272,58 @@ export function PreviewWorkflow({
300272
return map
301273
}, [workflowState.blocks, isValidWorkflowState])
302274

303-
/** Derives subflow execution status from child blocks */
275+
/** Maps base block IDs to execution data, handling parallel iteration variants (blockId₍n₎). */
276+
const blockExecutionMap = useMemo(() => {
277+
if (!executedBlocks) return new Map<string, { status: string }>()
278+
279+
const map = new Map<string, { status: string }>()
280+
for (const [key, value] of Object.entries(executedBlocks)) {
281+
// Extract base ID (remove iteration suffix like ₍0₎)
282+
const baseId = key.includes('₍') ? key.split('₍')[0] : key
283+
// Keep first match or error status (error takes precedence)
284+
const existing = map.get(baseId)
285+
if (!existing || value.status === 'error') {
286+
map.set(baseId, value)
287+
}
288+
}
289+
return map
290+
}, [executedBlocks])
291+
292+
/** Derives subflow status from children. Error takes precedence. */
304293
const getSubflowExecutionStatus = useMemo(() => {
305294
return (subflowId: string): ExecutionStatus | undefined => {
306-
if (!executedBlocks) return undefined
307-
308295
const childIds = subflowChildrenMap.get(subflowId)
309296
if (!childIds?.length) return undefined
310297

311-
const childStatuses = childIds.map((id) => executedBlocks[id]).filter(Boolean)
312-
if (childStatuses.length === 0) return undefined
298+
const executedChildren = childIds
299+
.map((id) => blockExecutionMap.get(id))
300+
.filter((status): status is { status: string } => Boolean(status))
313301

314-
if (childStatuses.some((s) => s.status === 'error')) return 'error'
315-
if (childStatuses.some((s) => s.status === 'success')) return 'success'
316-
return 'not-executed'
302+
if (executedChildren.length === 0) return undefined
303+
if (executedChildren.some((s) => s.status === 'error')) return 'error'
304+
return 'success'
317305
}
318-
}, [executedBlocks, subflowChildrenMap])
306+
}, [subflowChildrenMap, blockExecutionMap])
319307

320-
/** Gets execution status for any block, deriving subflow status from children */
308+
/** Gets block status. Subflows derive status from children. */
321309
const getBlockExecutionStatus = useMemo(() => {
322310
return (blockId: string): { status: string; executed: boolean } | undefined => {
323-
if (!executedBlocks) return undefined
324-
325-
const directStatus = executedBlocks[blockId]
311+
const directStatus = blockExecutionMap.get(blockId)
326312
if (directStatus) {
327313
return { status: directStatus.status, executed: true }
328314
}
329315

330316
const block = workflowState.blocks?.[blockId]
331-
if (block && (block.type === 'loop' || block.type === 'parallel')) {
317+
if (block?.type === 'loop' || block?.type === 'parallel') {
332318
const subflowStatus = getSubflowExecutionStatus(blockId)
333319
if (subflowStatus) {
334320
return { status: subflowStatus, executed: true }
335321
}
336-
337-
const incomingEdge = workflowState.edges?.find((e) => e.target === blockId)
338-
if (incomingEdge && executedBlocks[incomingEdge.source]?.status === 'success') {
339-
return { status: 'not-executed', executed: true }
340-
}
341322
}
342323

343324
return undefined
344325
}
345-
}, [executedBlocks, workflowState.blocks, workflowState.edges, getSubflowExecutionStatus])
326+
}, [workflowState.blocks, getSubflowExecutionStatus, blockExecutionMap])
346327

347328
const edgesStructure = useMemo(() => {
348329
if (!isValidWorkflowState) return { count: 0, ids: '' }
@@ -444,48 +425,29 @@ export function PreviewWorkflow({
444425
const edges: Edge[] = useMemo(() => {
445426
if (!isValidWorkflowState) return []
446427

447-
/**
448-
* Determines edge execution status for visualization.
449-
* Error edges turn green when taken (source errored, target executed).
450-
* Normal edges turn green when both source succeeded and target executed.
451-
*/
428+
/** Edge is green if target executed and source condition met by edge type. */
452429
const getEdgeExecutionStatus = (edge: {
453430
source: string
454431
target: string
455432
sourceHandle?: string | null
456433
}): ExecutionStatus | undefined => {
457-
if (!executedBlocks) return undefined
434+
if (blockExecutionMap.size === 0) return undefined
458435

459-
const sourceStatus = getBlockExecutionStatus(edge.source)
460436
const targetStatus = getBlockExecutionStatus(edge.target)
461-
const isErrorEdge = edge.sourceHandle === 'error'
462-
463-
if (isErrorEdge) {
464-
return sourceStatus?.status === 'error' && targetStatus?.executed
465-
? 'success'
466-
: 'not-executed'
467-
}
437+
if (!targetStatus?.executed) return 'not-executed'
468438

469-
const isSubflowStartEdge =
470-
edge.sourceHandle === 'loop-start-source' || edge.sourceHandle === 'parallel-start-source'
439+
const sourceStatus = getBlockExecutionStatus(edge.source)
440+
const { sourceHandle } = edge
471441

472-
if (isSubflowStartEdge) {
473-
const incomingEdge = workflowState.edges?.find((e) => e.target === edge.source)
474-
const incomingSucceeded = incomingEdge
475-
? executedBlocks[incomingEdge.source]?.status === 'success'
476-
: false
477-
return incomingSucceeded ? 'success' : 'not-executed'
442+
if (sourceHandle === 'error') {
443+
return sourceStatus?.status === 'error' ? 'success' : 'not-executed'
478444
}
479445

480-
const targetBlock = workflowState.blocks?.[edge.target]
481-
const targetIsSubflow =
482-
targetBlock && (targetBlock.type === 'loop' || targetBlock.type === 'parallel')
483-
484-
if (sourceStatus?.status === 'success' && (targetStatus?.executed || targetIsSubflow)) {
446+
if (sourceHandle === 'loop-start-source' || sourceHandle === 'parallel-start-source') {
485447
return 'success'
486448
}
487449

488-
return 'not-executed'
450+
return sourceStatus?.status === 'success' ? 'success' : 'not-executed'
489451
}
490452

491453
return (workflowState.edges || []).map((edge) => {
@@ -507,9 +469,8 @@ export function PreviewWorkflow({
507469
}, [
508470
edgesStructure,
509471
workflowState.edges,
510-
workflowState.blocks,
511472
isValidWorkflowState,
512-
executedBlocks,
473+
blockExecutionMap,
513474
getBlockExecutionStatus,
514475
])
515476

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/invite-modal/invite-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function InviteModal({ open, onOpenChange, workspaceName }: InviteModalPr
164164
...prev,
165165
{
166166
email: normalized,
167-
permissionType: 'read',
167+
permissionType: 'admin',
168168
},
169169
])
170170
}

0 commit comments

Comments
 (0)