Skip to content

Commit 2e08162

Browse files
authored
improvement(editor): render tool permission mode and retry fields with standard sub-block inputs (#7834)
* improvement(editor): render tool permission mode and retry fields with standard sub-block inputs * fix(editor): turn off reference pickers on retry number fields
1 parent 89a8b12 commit 2e08162

7 files changed

Lines changed: 196 additions & 82 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/retry-settings/retry-settings.test.tsx‎

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@
44
import { act } from 'react'
55
import { createRoot, type Root } from 'react-dom/client'
66
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
7+
8+
vi.mock(
9+
'@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input',
10+
() => ({
11+
ShortInput: ({
12+
config,
13+
value,
14+
onChange,
15+
onBlur,
16+
disabled,
17+
allowReferences,
18+
}: {
19+
config: { id: string }
20+
value: string
21+
onChange: (value: string) => void
22+
onBlur: () => void
23+
disabled: boolean
24+
allowReferences?: boolean
25+
}) => (
26+
<input
27+
id={config.id}
28+
data-allow-references={String(allowReferences ?? true)}
29+
value={value}
30+
onChange={(event) => onChange(event.target.value)}
31+
onBlur={onBlur}
32+
disabled={disabled}
33+
/>
34+
),
35+
})
36+
)
37+
738
import { RetrySettings } from './retry-settings'
839

940
const policy = { enabled: true as const, maxTries: 5, waitBetweenTriesMs: 2000 }
@@ -25,7 +56,15 @@ afterEach(() => {
2556
function renderSettings(props: Partial<Parameters<typeof RetrySettings>[0]> = {}) {
2657
const onChange = vi.fn()
2758
act(() => {
28-
root.render(<RetrySettings retry={policy} disabled={false} onChange={onChange} {...props} />)
59+
root.render(
60+
<RetrySettings
61+
blockId='block-1'
62+
retry={policy}
63+
disabled={false}
64+
onChange={onChange}
65+
{...props}
66+
/>
67+
)
2968
})
3069
return { onChange }
3170
}
@@ -46,6 +85,33 @@ describe('RetrySettings', () => {
4685
expect(field('block-retry-max-tries')!.value).toBe('5')
4786
})
4887

88+
it('commits the normalized value when the field loses focus', () => {
89+
const { onChange } = renderSettings()
90+
const maxTries = field('block-retry-max-tries')!
91+
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!
92+
93+
act(() => {
94+
setValue.call(maxTries, '2.7')
95+
maxTries.dispatchEvent(new Event('input', { bubbles: true }))
96+
})
97+
expect(field('block-retry-max-tries')!.value).toBe('2.7')
98+
expect(onChange).not.toHaveBeenCalled()
99+
100+
act(() => {
101+
maxTries.dispatchEvent(new FocusEvent('focusout', { bubbles: true }))
102+
})
103+
104+
expect(onChange).toHaveBeenCalledWith({ ...policy, maxTries: 2 })
105+
expect(field('block-retry-max-tries')!.value).toBe('5')
106+
})
107+
108+
it('turns off the reference pickers on the numeric fields', () => {
109+
renderSettings()
110+
111+
expect(field('block-retry-max-tries')!.dataset.allowReferences).toBe('false')
112+
expect(field('block-retry-wait')!.dataset.allowReferences).toBe('false')
113+
})
114+
49115
it('renders only the switch while retry is off', () => {
50116
renderSettings({ retry: { ...policy, enabled: false } })
51117

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/retry-settings/retry-settings.tsx‎

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,63 @@
11
'use client'
22

33
import { useState } from 'react'
4-
import { ChipInput, FieldDivider, Label, Switch } from '@sim/emcn'
4+
import { FieldDivider, Label, Switch } from '@sim/emcn'
55
import {
66
BLOCK_RETRY_DEFAULT_TRIES,
77
BLOCK_RETRY_DEFAULT_WAIT_MS,
88
type BlockRetryConfig,
99
normalizeBlockRetryTries,
1010
normalizeBlockRetryWaitMs,
1111
} from '@sim/workflow-types/workflow'
12+
import { ShortInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input'
13+
import type { SubBlockConfig } from '@/blocks/types'
1214

1315
interface RetrySettingsProps {
16+
blockId: string
1417
retry: BlockRetryConfig | undefined
1518
disabled: boolean
1619
onChange: (retry: BlockRetryConfig) => void
1720
}
1821

1922
interface RetryNumberFieldProps {
20-
id: string
21-
title: string
23+
blockId: string
24+
config: SubBlockConfig
2225
value: number
2326
disabled: boolean
2427
normalize: (value: unknown) => number
2528
onCommit: (value: number) => void
2629
}
2730

31+
const MAX_TRIES_CONFIG = {
32+
id: 'block-retry-max-tries',
33+
title: 'Max tries',
34+
type: 'short-input',
35+
connectionDroppable: false,
36+
} as const satisfies SubBlockConfig
37+
38+
const WAIT_CONFIG = {
39+
id: 'block-retry-wait',
40+
title: 'Wait between tries (ms)',
41+
type: 'short-input',
42+
connectionDroppable: false,
43+
} as const satisfies SubBlockConfig
44+
2845
/**
2946
* A bounded number field that commits on blur.
3047
*
31-
* Typed as text with a numeric input mode rather than `type='number'`: the
32-
* native spinner is all that buys, and it does not fit the field chrome the rest
33-
* of the panel uses. Bounds are applied on commit through the same normalizer
34-
* execution uses, so the field cannot clamp differently from the executor.
48+
* Renders the same `ShortInput` every other sub-block text field uses, so it
49+
* carries the panel's field chrome. Retry values are plain numbers that never
50+
* resolve references, so the reference pickers are turned off. Bounds are
51+
* applied on commit through the same normalizer execution uses, so the field
52+
* cannot clamp differently from the executor.
3553
*
3654
* The draft exists only while the field is being edited; clearing it on commit
3755
* lets an external change — a collaborator's edit, or an undo — flow straight
3856
* through on the next render with no resync.
3957
*/
4058
function RetryNumberField({
41-
id,
42-
title,
59+
blockId,
60+
config,
4361
value,
4462
disabled,
4563
normalize,
@@ -57,15 +75,18 @@ function RetryNumberField({
5775

5876
return (
5977
<div className='subblock-content flex flex-col gap-2.5'>
60-
<Label htmlFor={id}>{title}</Label>
61-
<ChipInput
62-
id={id}
63-
type='text'
64-
inputMode='numeric'
78+
<div className='flex items-center justify-between gap-1.5 pl-0.5'>
79+
<Label className='flex items-baseline gap-1.5 whitespace-nowrap'>{config.title}</Label>
80+
</div>
81+
<ShortInput
82+
blockId={blockId}
83+
subBlockId={config.id}
84+
config={config}
6585
value={draft ?? String(value)}
66-
onChange={(event) => setDraft(event.target.value)}
86+
onChange={setDraft}
6787
onBlur={commit}
6888
disabled={disabled}
89+
allowReferences={false}
6990
/>
7091
</div>
7192
)
@@ -79,7 +100,7 @@ function RetryNumberField({
79100
* with `enabled: false` when it is switched off, so turning it back on restores
80101
* what was configured rather than snapping to the defaults.
81102
*/
82-
export function RetrySettings({ retry, disabled, onChange }: RetrySettingsProps) {
103+
export function RetrySettings({ blockId, retry, disabled, onChange }: RetrySettingsProps) {
83104
const enabled = retry?.enabled === true
84105
const maxTries = retry?.maxTries ?? BLOCK_RETRY_DEFAULT_TRIES
85106
const waitBetweenTriesMs = retry?.waitBetweenTriesMs ?? BLOCK_RETRY_DEFAULT_WAIT_MS
@@ -106,8 +127,8 @@ export function RetrySettings({ retry, disabled, onChange }: RetrySettingsProps)
106127
<>
107128
<div className='subblock-row'>
108129
<RetryNumberField
109-
id='block-retry-max-tries'
110-
title='Max tries'
130+
blockId={blockId}
131+
config={MAX_TRIES_CONFIG}
111132
value={maxTries}
112133
disabled={disabled}
113134
normalize={normalizeBlockRetryTries}
@@ -117,8 +138,8 @@ export function RetrySettings({ retry, disabled, onChange }: RetrySettingsProps)
117138
</div>
118139
<div className='subblock-row'>
119140
<RetryNumberField
120-
id='block-retry-wait'
121-
title='Wait between tries (ms)'
141+
blockId={blockId}
142+
config={WAIT_CONFIG}
122143
value={waitBetweenTriesMs}
123144
disabled={disabled}
124145
normalize={normalizeBlockRetryWaitMs}

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input/short-input.tsx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ interface ShortInputProps {
5252
/** Whether to hide the internal wand button (controlled by parent) */
5353
hideInternalWand?: boolean
5454
workflowSearchValuePath?: Array<string | number>
55+
/** Whether the env-var and tag reference pickers may open. Defaults to `true`. */
56+
allowReferences?: boolean
57+
/** Called when the input loses focus. */
58+
onBlur?: () => void
5559
}
5660

5761
/**
@@ -81,6 +85,8 @@ export const ShortInput = memo(function ShortInput({
8185
wandControlRef,
8286
hideInternalWand = false,
8387
workflowSearchValuePath = [],
88+
allowReferences = true,
89+
onBlur,
8490
}: ShortInputProps) {
8591
const activeSearchTarget = useActiveSearchTarget()
8692
const [localContent, setLocalContent] = useState<string>('')
@@ -284,7 +290,8 @@ export const ShortInput = memo(function ShortInput({
284290

285291
const handleBlur = useCallback(() => {
286292
setIsFocused(false)
287-
}, [])
293+
onBlur?.()
294+
}, [onBlur])
288295

289296
// Expose wand control handlers to parent via ref
290297
useImperativeHandle(
@@ -325,6 +332,7 @@ export const ShortInput = memo(function ShortInput({
325332
disabled={disabled}
326333
isStreaming={wandHook.isStreaming}
327334
previewValue={previewValue}
335+
allowReferences={allowReferences}
328336
shouldForceEnvDropdown={shouldForceEnvDropdown}
329337
shouldForceTagDropdown={shouldForceTagDropdown}
330338
>

‎apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/sub-block-input-controller/sub-block-input-controller.tsx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export interface SubBlockInputControllerProps {
3333
onStreamingEnd?: () => void
3434
/** Optional preview value for read-only preview. */
3535
previewValue?: string | null
36+
/** Whether the env-var and tag reference pickers may open. Defaults to `true`. */
37+
allowReferences?: boolean
3638
/**
3739
* Optional callback to force/show the env var dropdown (e.g., API key fields).
3840
* Return { show: true, searchTerm?: string } to override defaults.
@@ -82,6 +84,7 @@ export function SubBlockInputController(props: SubBlockInputControllerProps): Re
8284
isStreaming,
8385
onStreamingEnd,
8486
previewValue,
87+
allowReferences,
8588
shouldForceEnvDropdown,
8689
shouldForceTagDropdown,
8790
children,
@@ -98,6 +101,7 @@ export function SubBlockInputController(props: SubBlockInputControllerProps): Re
98101
isStreaming,
99102
onStreamingEnd,
100103
previewValue,
104+
allowReferences,
101105
shouldForceEnvDropdown,
102106
shouldForceTagDropdown,
103107
})

0 commit comments

Comments
 (0)