Skip to content

Commit 1263662

Browse files
committed
Merge branch 'workflow-updates-v2' of github.com:simstudioai/sim into workflow-updates-v2
2 parents a4169cb + beeabd6 commit 1263662

45 files changed

Lines changed: 19874 additions & 115 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/realtime/src/database/operations.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,35 @@ async function handleBlockOperationTx(
768768
break
769769
}
770770

771+
case BLOCK_OPERATIONS.UPDATE_RETRY: {
772+
if (!payload.id || payload.retry === undefined) {
773+
throw new Error('Missing required fields for update retry operation')
774+
}
775+
776+
const updateResult = await tx
777+
.update(workflowBlocks)
778+
.set({
779+
/**
780+
* Persisted verbatim, including a disabled policy, so the numbers a
781+
* builder configured survive switching retry off and back on. NULL stays
782+
* reserved for a block that never had a policy at all; whether a stored
783+
* policy actually runs is decided by `resolveBlockRetryConfig` at
784+
* execution time, never by the column being present.
785+
*/
786+
retry: payload.retry,
787+
updatedAt: new Date(),
788+
})
789+
.where(and(eq(workflowBlocks.id, payload.id), eq(workflowBlocks.workflowId, workflowId)))
790+
.returning({ id: workflowBlocks.id })
791+
792+
if (updateResult.length === 0) {
793+
throw new Error(`Block ${payload.id} not found in workflow ${workflowId}`)
794+
}
795+
796+
logger.debug(`Updated block retry: ${payload.id} -> ${payload.retry.enabled}`)
797+
break
798+
}
799+
771800
case BLOCK_OPERATIONS.UPDATE_CANONICAL_MODE: {
772801
if (!payload.id || !payload.canonicalId || !payload.canonicalMode) {
773802
throw new Error('Missing required fields for update canonical mode operation')
@@ -962,6 +991,7 @@ async function handleBlocksOperationTx(
962991
advancedMode: (block.advancedMode as boolean) ?? false,
963992
triggerMode: (block.triggerMode as boolean) ?? false,
964993
errorEnabled: (block.errorEnabled as boolean) ?? false,
994+
retry: (block.retry as Record<string, unknown> | undefined) ?? null,
965995
height: (block.height as number) || 0,
966996
locked: (block.locked as boolean) ?? false,
967997
}
@@ -981,6 +1011,7 @@ async function handleBlocksOperationTx(
9811011
horizontalHandles: sql`excluded.horizontal_handles`,
9821012
advancedMode: sql`excluded.advanced_mode`,
9831013
triggerMode: sql`excluded.trigger_mode`,
1014+
retry: sql`excluded.retry`,
9841015
locked: sql`excluded.locked`,
9851016
height: sql`excluded.height`,
9861017
subBlocks: sql`excluded.sub_blocks`,
@@ -2172,6 +2203,7 @@ async function handleWorkflowOperationTx(
21722203
positionX: block.position.x,
21732204
positionY: block.position.y,
21742205
errorEnabled: block.errorEnabled ?? false,
2206+
retry: block.retry ?? null,
21752207
data: block.data || {},
21762208
subBlocks: block.subBlocks || {},
21772209
outputs: block.outputs || {},

apps/realtime/src/middleware/permissions.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* - Edge cases and invalid inputs
88
*/
99

10-
import { ALL_SOCKET_OPERATIONS } from '@sim/realtime-protocol/constants'
10+
import { ALL_SOCKET_OPERATIONS, BLOCK_OPERATIONS } from '@sim/realtime-protocol/constants'
1111
import {
1212
expectPermissionAllowed,
1313
expectPermissionDenied,
@@ -232,6 +232,17 @@ describe('checkRolePermission', () => {
232232
expect(adminOnly.length).toBeGreaterThan(0)
233233
})
234234

235+
it('grants write every per-block operation the protocol declares', () => {
236+
// A block operation that reaches this gate is an ordinary editor edit, so the
237+
// write role must hold all of them. Without this, adding a block setting to
238+
// the protocol and forgetting the ACL entry fails silently at runtime: the
239+
// editor applies the change optimistically and the server drops the write.
240+
const denied = Object.values(BLOCK_OPERATIONS).filter(
241+
(operation) => !checkRolePermission('write', operation).allowed
242+
)
243+
expect(denied).toEqual([])
244+
})
245+
235246
it('grants read nothing, so it is trivially a subset of write', () => {
236247
const readAllowed = ALL_SOCKET_OPERATIONS.filter(
237248
(operation) => checkRolePermission('read', operation).allowed

apps/realtime/src/middleware/permissions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const WRITE_OPERATIONS: string[] = [
3030
BLOCK_OPERATIONS.UPDATE_PARENT,
3131
BLOCK_OPERATIONS.UPDATE_ADVANCED_MODE,
3232
BLOCK_OPERATIONS.UPDATE_ERROR_ENABLED,
33+
BLOCK_OPERATIONS.UPDATE_RETRY,
3334
BLOCK_OPERATIONS.UPDATE_CANONICAL_MODE,
3435
BLOCK_OPERATIONS.REPLACE_CANONICAL_MODES,
3536
BLOCK_OPERATIONS.TOGGLE_HANDLES,

apps/sim/app/playground/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -868,10 +868,10 @@ export default function PlaygroundPage() {
868868
</PopoverContent>
869869
</Popover>
870870
</VariantRow>
871-
<VariantRow label='secondary variant'>
872-
<Popover variant='secondary'>
871+
<VariantRow label='inverted color scheme'>
872+
<Popover colorScheme='inverted'>
873873
<PopoverTrigger asChild>
874-
<Button variant='secondary'>Secondary Popover</Button>
874+
<Button variant='secondary'>Inverted Popover</Button>
875875
</PopoverTrigger>
876876
<PopoverContent>
877877
<PopoverItem>Item 1</PopoverItem>

apps/sim/app/workspace/[workspaceId]/logs/components/log-details/components/trace-view/trace-view.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
673673
value: isCustomBlockType(span.type) ? 'custom block' : span.type,
674674
})
675675
metaEntries.push({ label: 'Duration', value: formatDuration(duration, { precision: 2 }) || '—' })
676+
if (span.tries !== undefined) metaEntries.push({ label: 'Tries', value: String(span.tries) })
676677
if (span.provider) metaEntries.push({ label: 'Provider', value: span.provider })
677678
if (span.model) metaEntries.push({ label: 'Model', value: span.model })
678679
if (span.finishReason) metaEntries.push({ label: 'Finish reason', value: span.finishReason })

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/block-menu/block-menu.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,7 @@ export function BlockMenu({
130130
}
131131

132132
return (
133-
<Popover
134-
open={isOpen}
135-
onOpenChange={(open) => !open && onClose()}
136-
variant='secondary'
137-
size='sm'
138-
colorScheme='inverted'
139-
>
133+
<Popover open={isOpen} onOpenChange={(open) => !open && onClose()} size='sm'>
140134
<PopoverAnchor
141135
style={{
142136
position: 'fixed',

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/canvas-menu/canvas-menu.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,7 @@ export function CanvasMenu({
7070
hasBlocks = false,
7171
}: CanvasMenuProps) {
7272
return (
73-
<Popover
74-
open={isOpen}
75-
onOpenChange={(open) => !open && onClose()}
76-
variant='secondary'
77-
size='sm'
78-
colorScheme='inverted'
79-
>
73+
<Popover open={isOpen} onOpenChange={(open) => !open && onClose()} size='sm'>
8074
<PopoverAnchor
8175
style={{
8276
position: 'fixed',

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ export function Chat() {
947947

948948
<div className='flex flex-shrink-0 items-center gap-2'>
949949
{/* More menu with actions */}
950-
<Popover variant='default' size='sm' open={moreMenuOpen} onOpenChange={setMoreMenuOpen}>
950+
<Popover size='sm' open={moreMenuOpen} onOpenChange={setMoreMenuOpen}>
951951
<PopoverTrigger asChild>
952952
<Button
953953
variant='ghost'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { ConnectionBlocks } from './connection-blocks/connection-blocks'
2+
export { RetrySettings } from './retry-settings/retry-settings'
23
export { SubBlock } from './sub-block/sub-block'
34
export { SubflowEditor } from './subflow-editor/subflow-editor'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { act } from 'react'
5+
import { createRoot, type Root } from 'react-dom/client'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
import { RetrySettings } from './retry-settings'
8+
9+
const policy = { enabled: true as const, maxTries: 5, waitBetweenTriesMs: 2000 }
10+
11+
let container: HTMLDivElement
12+
let root: Root
13+
14+
beforeEach(() => {
15+
container = document.createElement('div')
16+
document.body.appendChild(container)
17+
root = createRoot(container)
18+
})
19+
20+
afterEach(() => {
21+
act(() => root.unmount())
22+
container.remove()
23+
})
24+
25+
function renderSettings(props: Partial<Parameters<typeof RetrySettings>[0]> = {}) {
26+
const onChange = vi.fn()
27+
act(() => {
28+
root.render(<RetrySettings retry={policy} disabled={false} onChange={onChange} {...props} />)
29+
})
30+
return { onChange }
31+
}
32+
33+
const field = (id: string) => container.querySelector<HTMLInputElement>(`#${id}`)
34+
35+
describe('RetrySettings', () => {
36+
it('leaves a configured value alone when the field is blurred untouched', () => {
37+
const { onChange } = renderSettings()
38+
const maxTries = field('block-retry-max-tries')!
39+
expect(maxTries.value).toBe('5')
40+
41+
act(() => {
42+
maxTries.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
43+
})
44+
45+
expect(onChange).not.toHaveBeenCalled()
46+
expect(field('block-retry-max-tries')!.value).toBe('5')
47+
})
48+
49+
it('renders only the switch while retry is off', () => {
50+
renderSettings({ retry: { ...policy, enabled: false } })
51+
52+
expect(field('block-retry-enabled')).not.toBeNull()
53+
expect(field('block-retry-max-tries')).toBeNull()
54+
})
55+
})

0 commit comments

Comments
 (0)