fix(serializer): default canonical modes construction (#3330)

* fix(serializer): default canonical modes construction

* defaults for copilot

* address bugbot comments
This commit is contained in:
Vikhyath Mondreti
2026-02-24 22:05:17 -08:00
committed by GitHub
parent ecdb133d1b
commit 58d0fda173
5 changed files with 72 additions and 30 deletions
@@ -2,7 +2,11 @@ import crypto from 'crypto'
import { createLogger } from '@sim/logger'
import type { PermissionGroupConfig } from '@/lib/permission-groups/types'
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { buildCanonicalIndex, isCanonicalPair } from '@/lib/workflows/subblocks/visibility'
import {
buildCanonicalIndex,
buildDefaultCanonicalModes,
isCanonicalPair,
} from '@/lib/workflows/subblocks/visibility'
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
import { getAllBlocks } from '@/blocks/registry'
import type { BlockConfig } from '@/blocks/types'
@@ -130,6 +134,12 @@ export function createBlockFromParams(
}
})
const defaultModes = buildDefaultCanonicalModes(blockConfig.subBlocks)
if (Object.keys(defaultModes).length > 0) {
if (!blockState.data) blockState.data = {}
blockState.data.canonicalModes = defaultModes
}
if (validatedInputs) {
updateCanonicalModesForInputs(blockState, Object.keys(validatedInputs), blockConfig)
}
@@ -75,6 +75,23 @@ export function isCanonicalPair(group?: CanonicalGroup): boolean {
return Boolean(group?.basicId && group?.advancedIds?.length)
}
/**
* Builds default canonical mode overrides for a block's subblocks.
* All canonical pairs default to `'basic'`.
*/
export function buildDefaultCanonicalModes(
subBlocks: SubBlockConfig[]
): Record<string, 'basic' | 'advanced'> {
const index = buildCanonicalIndex(subBlocks)
const modes: Record<string, 'basic' | 'advanced'> = {}
for (const group of Object.values(index.groupsById)) {
if (isCanonicalPair(group)) {
modes[group.canonicalId] = 'basic'
}
}
return modes
}
/**
* Determine the active mode for a canonical group.
*/
+29 -26
View File
@@ -683,34 +683,37 @@ describe('Serializer', () => {
expect(slackBlock?.config.params.username).toBe('bot')
})
it.concurrent('should fall back to legacy advancedMode when canonicalModes not set', () => {
const serializer = new Serializer()
it.concurrent(
'should fall back to legacy advancedMode for non-credential canonical groups when canonicalModes not set',
() => {
const serializer = new Serializer()
const block: any = {
id: 'slack-1',
type: 'slack',
name: 'Test Slack Block',
position: { x: 0, y: 0 },
advancedMode: true,
subBlocks: {
operation: { value: 'send' },
destinationType: { value: 'channel' },
channel: { value: 'general' },
manualChannel: { value: 'C1234567890' },
text: { value: 'Hello world' },
username: { value: 'bot' },
},
outputs: {},
enabled: true,
const block: any = {
id: 'slack-1',
type: 'slack',
name: 'Test Slack Block',
position: { x: 0, y: 0 },
advancedMode: true,
subBlocks: {
operation: { value: 'send' },
destinationType: { value: 'channel' },
channel: { value: 'general' },
manualChannel: { value: 'C1234567890' },
text: { value: 'Hello world' },
username: { value: 'bot' },
},
outputs: {},
enabled: true,
}
const serialized = serializer.serializeWorkflow({ 'slack-1': block }, [], {})
const slackBlock = serialized.blocks.find((b) => b.id === 'slack-1')
expect(slackBlock).toBeDefined()
expect(slackBlock?.config.params.channel).toBe('C1234567890')
expect(slackBlock?.config.params.manualChannel).toBeUndefined()
}
const serialized = serializer.serializeWorkflow({ 'slack-1': block }, [], {})
const slackBlock = serialized.blocks.find((b) => b.id === 'slack-1')
expect(slackBlock).toBeDefined()
expect(slackBlock?.config.params.channel).toBe('C1234567890')
expect(slackBlock?.config.params.manualChannel).toBeUndefined()
})
)
it.concurrent('should use basic value by default when no mode specified', () => {
const serializer = new Serializer()
+7 -3
View File
@@ -61,8 +61,9 @@ function shouldSerializeSubBlock(
const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined
if (group && isCanonicalPair(group)) {
const mode =
canonicalModeOverrides?.[group.canonicalId] ??
(displayAdvancedOptions ? 'advanced' : resolveCanonicalMode(group, values))
canonicalModeOverrides?.[group.canonicalId] != null || !displayAdvancedOptions
? resolveCanonicalMode(group, values, canonicalModeOverrides)
: 'advanced'
const matchesMode =
mode === 'advanced'
? group.advancedIds.includes(subBlockConfig.id)
@@ -374,8 +375,11 @@ export class Serializer {
Object.values(canonicalIndex.groupsById).forEach((group) => {
const { basicValue, advancedValue } = getCanonicalValues(group, params)
const hasExplicitOverride = canonicalModeOverrides?.[group.canonicalId] != null
const pairMode =
canonicalModeOverrides?.[group.canonicalId] ?? (legacyAdvancedMode ? 'advanced' : 'basic')
hasExplicitOverride || !legacyAdvancedMode
? resolveCanonicalMode(group, allValues, canonicalModeOverrides)
: 'advanced'
const chosen = pairMode === 'advanced' ? advancedValue : basicValue
const sourceIds = [group.basicId, ...group.advancedIds].filter(Boolean) as string[]
+8
View File
@@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'
import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants'
import { getEffectiveBlockOutputs } from '@/lib/workflows/blocks/block-outputs'
import { mergeSubblockStateWithValues } from '@/lib/workflows/subblocks'
import { buildDefaultCanonicalModes } from '@/lib/workflows/subblocks/visibility'
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
import { getBlock } from '@/blocks'
@@ -196,6 +197,13 @@ export function prepareBlockState(options: PrepareBlockStateOptions): BlockState
preferToolOutputs: !effectiveTriggerMode,
})
if (blockConfig.subBlocks) {
const canonicalModes = buildDefaultCanonicalModes(blockConfig.subBlocks)
if (Object.keys(canonicalModes).length > 0) {
blockData.canonicalModes = canonicalModes
}
}
return {
id,
type,