Compare commits

...

4 Commits

Author SHA1 Message Date
Vikhyath Mondreti
fc5df60d8f remove dead code 2026-03-06 18:37:32 -08:00
Vikhyath Mondreti
adea9db89d another workflowid pass through 2026-03-06 18:25:36 -08:00
Vikhyath Mondreti
94abc424be fix resolve values fallback 2026-03-06 18:18:25 -08:00
Vikhyath Mondreti
c1c6ed66d1 improvement(selectors): simplify selectorContext + add tests 2026-03-06 18:03:40 -08:00
10 changed files with 323 additions and 307 deletions

View File

@@ -2,6 +2,7 @@
import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { SELECTOR_CONTEXT_FIELDS } from '@/lib/workflows/subblocks/context'
import type { SubBlockConfig } from '@/blocks/types'
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
@@ -14,8 +15,7 @@ import { useDependsOnGate } from './use-depends-on-gate'
*
* Builds a `SelectorContext` by mapping each `dependsOn` entry through the
* canonical index to its `canonicalParamId`, which maps directly to
* `SelectorContext` field names (e.g. `siteId`, `teamId`, `collectionId`).
* The one special case is `oauthCredential` which maps to `credentialId`.
* `SelectorContext` field names (e.g. `siteId`, `teamId`, `oauthCredential`).
*
* @param blockId - The block containing the selector sub-block
* @param subBlock - The sub-block config (must have `selectorKey` set)
@@ -70,11 +70,8 @@ export function useSelectorSetup(
if (isReference(strValue)) continue
const canonicalParamId = canonicalIndex.canonicalIdBySubBlockId[depKey] ?? depKey
if (canonicalParamId === 'oauthCredential') {
context.credentialId = strValue
} else if (canonicalParamId in CONTEXT_FIELD_SET) {
;(context as Record<string, unknown>)[canonicalParamId] = strValue
if (SELECTOR_CONTEXT_FIELDS.has(canonicalParamId as keyof SelectorContext)) {
context[canonicalParamId as keyof SelectorContext] = strValue
}
}
@@ -89,19 +86,3 @@ export function useSelectorSetup(
dependencyValues: resolvedDependencyValues,
}
}
const CONTEXT_FIELD_SET: Record<string, true> = {
credentialId: true,
domain: true,
teamId: true,
projectId: true,
knowledgeBaseId: true,
planId: true,
siteId: true,
collectionId: true,
spreadsheetId: true,
fileId: true,
baseId: true,
datasetId: true,
serviceDeskId: true,
}

View File

@@ -57,9 +57,9 @@ import { useWebhookManagement } from '@/hooks/use-webhook-management'
const SLACK_OVERRIDES: SelectorOverrides = {
transformContext: (context, deps) => {
const authMethod = deps.authMethod as string
const credentialId =
const oauthCredential =
authMethod === 'bot_token' ? String(deps.botToken ?? '') : String(deps.credential ?? '')
return { ...context, credentialId }
return { ...context, oauthCredential }
},
}

View File

@@ -578,7 +578,7 @@ const SubBlockRow = memo(function SubBlockRow({
subBlock,
value: rawValue,
workflowId,
credentialId: typeof credentialId === 'string' ? credentialId : undefined,
oauthCredential: typeof credentialId === 'string' ? credentialId : undefined,
knowledgeBaseId: typeof knowledgeBaseId === 'string' ? knowledgeBaseId : undefined,
domain: domainValue,
teamId: teamIdValue,

View File

@@ -39,10 +39,10 @@ type FolderResponse = { id: string; name: string }
type PlannerTask = { id: string; title: string }
const ensureCredential = (context: SelectorContext, key: SelectorKey): string => {
if (!context.credentialId) {
if (!context.oauthCredential) {
throw new Error(`Missing credential for selector ${key}`)
}
return context.credentialId
return context.oauthCredential
}
const ensureDomain = (context: SelectorContext, key: SelectorKey): string => {
@@ -66,9 +66,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'airtable.bases',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'airtable.bases')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -104,10 +104,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'airtable.tables',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.baseId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.baseId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.baseId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'airtable.tables')
if (!context.baseId) {
@@ -151,9 +151,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'asana.workspaces',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'asana.workspaces')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -182,9 +182,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'attio.objects',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'attio.objects')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -216,9 +216,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'attio.lists',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'attio.lists')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -250,10 +250,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'bigquery.datasets',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.projectId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.projectId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.projectId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'bigquery.datasets')
if (!context.projectId) throw new Error('Missing project ID for bigquery.datasets selector')
@@ -298,12 +298,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'bigquery.tables',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.projectId ?? 'none',
context.datasetId ?? 'none',
],
enabled: ({ context }) =>
Boolean(context.credentialId && context.projectId && context.datasetId),
Boolean(context.oauthCredential && context.projectId && context.datasetId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'bigquery.tables')
if (!context.projectId) throw new Error('Missing project ID for bigquery.tables selector')
@@ -347,9 +347,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'calcom.eventTypes',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'calcom.eventTypes')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -381,9 +381,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'calcom.schedules',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'calcom.schedules')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -415,10 +415,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'confluence.spaces',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'confluence.spaces')
const domain = ensureDomain(context, 'confluence.spaces')
@@ -460,10 +460,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'jsm.serviceDesks',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'jsm.serviceDesks')
const domain = ensureDomain(context, 'jsm.serviceDesks')
@@ -505,12 +505,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'jsm.requestTypes',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
context.serviceDeskId ?? 'none',
],
enabled: ({ context }) =>
Boolean(context.credentialId && context.domain && context.serviceDeskId),
Boolean(context.oauthCredential && context.domain && context.serviceDeskId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'jsm.requestTypes')
const domain = ensureDomain(context, 'jsm.requestTypes')
@@ -556,9 +556,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'google.tasks.lists',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'google.tasks.lists')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -587,9 +587,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.planner.plans',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.planner.plans')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -618,9 +618,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'notion.databases',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'notion.databases')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -652,9 +652,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'notion.pages',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'notion.pages')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -686,9 +686,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'pipedrive.pipelines',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'pipedrive.pipelines')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -720,10 +720,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'sharepoint.lists',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.siteId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.siteId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'sharepoint.lists')
if (!context.siteId) throw new Error('Missing site ID for sharepoint.lists selector')
@@ -761,9 +761,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'trello.boards',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'trello.boards')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -794,9 +794,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'zoom.meetings',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'zoom.meetings')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -828,12 +828,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'slack.channels',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({
credential: context.credentialId,
credential: context.oauthCredential,
workflowId: context.workflowId,
})
const data = await fetchJson<{ channels: SlackChannel[] }>('/api/tools/slack/channels', {
@@ -852,12 +852,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'slack.users',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({
credential: context.credentialId,
credential: context.oauthCredential,
workflowId: context.workflowId,
})
const data = await fetchJson<{ users: SlackUser[] }>('/api/tools/slack/users', {
@@ -876,12 +876,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'gmail.labels',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const data = await fetchJson<{ labels: FolderResponse[] }>('/api/tools/gmail/labels', {
searchParams: { credentialId: context.credentialId },
searchParams: { credentialId: context.oauthCredential },
})
return (data.labels || []).map((label) => ({
id: label.id,
@@ -895,12 +895,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'outlook.folders',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const data = await fetchJson<{ folders: FolderResponse[] }>('/api/tools/outlook/folders', {
searchParams: { credentialId: context.credentialId },
searchParams: { credentialId: context.oauthCredential },
})
return (data.folders || []).map((folder) => ({
id: folder.id,
@@ -914,13 +914,13 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'google.calendar',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const data = await fetchJson<{ calendars: { id: string; summary: string }[] }>(
'/api/tools/google_calendar/calendars',
{ searchParams: { credentialId: context.credentialId } }
{ searchParams: { credentialId: context.oauthCredential } }
)
return (data.calendars || []).map((calendar) => ({
id: calendar.id,
@@ -934,11 +934,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.teams',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({ credential: context.credentialId })
const body = JSON.stringify({ credential: context.oauthCredential })
const data = await fetchJson<{ teams: { id: string; displayName: string }[] }>(
'/api/tools/microsoft-teams/teams',
{ method: 'POST', body }
@@ -955,11 +955,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.chats',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({ credential: context.credentialId })
const body = JSON.stringify({ credential: context.oauthCredential })
const data = await fetchJson<{ chats: { id: string; displayName: string }[] }>(
'/api/tools/microsoft-teams/chats',
{ method: 'POST', body }
@@ -976,13 +976,13 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.channels',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.teamId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.teamId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({
credential: context.credentialId,
credential: context.oauthCredential,
teamId: context.teamId,
})
const data = await fetchJson<{ channels: { id: string; displayName: string }[] }>(
@@ -1001,14 +1001,14 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'wealthbox.contacts',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const data = await fetchJson<{ items: { id: string; name: string }[] }>(
'/api/tools/wealthbox/items',
{
searchParams: { credentialId: context.credentialId, type: 'contact' },
searchParams: { credentialId: context.oauthCredential, type: 'contact' },
}
)
return (data.items || []).map((item) => ({
@@ -1023,9 +1023,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'sharepoint.sites',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'sharepoint.sites')
const body = JSON.stringify({
@@ -1069,10 +1069,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.planner',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.planId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.planId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.planId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.planner')
const body = JSON.stringify({
@@ -1112,11 +1112,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'jira.projects',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'jira.projects')
const domain = ensureDomain(context, 'jira.projects')
@@ -1171,12 +1171,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'jira.issues',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
context.projectId ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'jira.issues')
const domain = ensureDomain(context, 'jira.issues')
@@ -1235,9 +1235,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'linear.teams',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'linear.teams')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -1260,10 +1260,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'linear.projects',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.teamId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.teamId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'linear.projects')
const body = JSON.stringify({
@@ -1290,11 +1290,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'confluence.pages',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.domain ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'confluence.pages')
const domain = ensureDomain(context, 'confluence.pages')
@@ -1343,9 +1343,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'onedrive.files',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'onedrive.files')
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
@@ -1366,9 +1366,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'onedrive.folders',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'onedrive.folders')
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
@@ -1389,12 +1389,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'google.drive',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.mimeType ?? 'any',
context.fileId ?? 'root',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'google.drive')
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
@@ -1438,10 +1438,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'google.sheets',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.spreadsheetId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'google.sheets')
if (!context.spreadsheetId) {
@@ -1469,10 +1469,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.excel.sheets',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.spreadsheetId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.excel.sheets')
if (!context.spreadsheetId) {
@@ -1500,10 +1500,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'microsoft.excel',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.excel')
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
@@ -1528,10 +1528,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'microsoft.word',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'microsoft.word')
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
@@ -1596,9 +1596,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'webflow.sites',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
enabled: ({ context }) => Boolean(context.oauthCredential),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.sites')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
@@ -1621,10 +1621,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'webflow.collections',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.siteId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.siteId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.collections')
if (!context.siteId) {
@@ -1654,11 +1654,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'webflow.items',
context.credentialId ?? 'none',
context.oauthCredential ?? 'none',
context.collectionId ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId && context.collectionId),
enabled: ({ context }) => Boolean(context.oauthCredential && context.collectionId),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.items')
if (!context.collectionId) {

View File

@@ -7,46 +7,16 @@ export interface SelectorResolution {
allowSearch: boolean
}
export interface SelectorResolutionArgs {
workflowId?: string
credentialId?: string
domain?: string
projectId?: string
planId?: string
teamId?: string
knowledgeBaseId?: string
siteId?: string
collectionId?: string
spreadsheetId?: string
fileId?: string
baseId?: string
datasetId?: string
serviceDeskId?: string
}
export function resolveSelectorForSubBlock(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs
context: SelectorContext
): SelectorResolution | null {
if (!subBlock.selectorKey) return null
return {
key: subBlock.selectorKey,
context: {
workflowId: args.workflowId,
credentialId: args.credentialId,
domain: args.domain,
projectId: args.projectId,
planId: args.planId,
teamId: args.teamId,
knowledgeBaseId: args.knowledgeBaseId,
siteId: args.siteId,
collectionId: args.collectionId,
spreadsheetId: args.spreadsheetId,
fileId: args.fileId,
baseId: args.baseId,
datasetId: args.datasetId,
serviceDeskId: args.serviceDeskId,
mimeType: subBlock.mimeType,
...context,
mimeType: subBlock.mimeType ?? context.mimeType,
},
allowSearch: subBlock.selectorAllowSearch ?? true,
}

View File

@@ -61,7 +61,7 @@ export interface SelectorOption {
export interface SelectorContext {
workspaceId?: string
workflowId?: string
credentialId?: string
oauthCredential?: string
serviceId?: string
domain?: string
teamId?: string

View File

@@ -12,7 +12,7 @@ interface SelectorDisplayNameArgs {
subBlock?: SubBlockConfig
value: unknown
workflowId?: string
credentialId?: string
oauthCredential?: string
domain?: string
projectId?: string
planId?: string
@@ -31,7 +31,7 @@ export function useSelectorDisplayName({
subBlock,
value,
workflowId,
credentialId,
oauthCredential,
domain,
projectId,
planId,
@@ -51,7 +51,7 @@ export function useSelectorDisplayName({
if (!subBlock || !detailId) return null
return resolveSelectorForSubBlock(subBlock, {
workflowId,
credentialId,
oauthCredential,
domain,
projectId,
planId,
@@ -69,7 +69,7 @@ export function useSelectorDisplayName({
subBlock,
detailId,
workflowId,
credentialId,
oauthCredential,
domain,
projectId,
planId,

View File

@@ -1,4 +1,5 @@
import { createLogger } from '@sim/logger'
import { buildSelectorContextFromBlock } from '@/lib/workflows/subblocks/context'
import { getBlock } from '@/blocks/registry'
import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types'
import { CREDENTIAL_SET, isUuid } from '@/executor/constants'
@@ -6,7 +7,7 @@ import { fetchCredentialSetById } from '@/hooks/queries/credential-sets'
import { fetchOAuthCredentialDetail } from '@/hooks/queries/oauth-credentials'
import { getSelectorDefinition } from '@/hooks/selectors/registry'
import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution'
import type { SelectorKey } from '@/hooks/selectors/types'
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
import type { WorkflowState } from '@/stores/workflows/workflow/types'
const logger = createLogger('ResolveValues')
@@ -39,74 +40,8 @@ interface ResolutionContext {
blockId?: string
}
/**
* Extended context extracted from block subBlocks for selector resolution
*/
interface ExtendedSelectorContext {
credentialId?: string
domain?: string
projectId?: string
planId?: string
teamId?: string
knowledgeBaseId?: string
siteId?: string
collectionId?: string
spreadsheetId?: string
baseId?: string
datasetId?: string
serviceDeskId?: string
}
function getSemanticFallback(subBlockId: string, subBlockConfig?: SubBlockConfig): string {
if (subBlockConfig?.title) {
return subBlockConfig.title.toLowerCase()
}
const patterns: Record<string, string> = {
credential: 'credential',
channel: 'channel',
channelId: 'channel',
user: 'user',
userId: 'user',
workflow: 'workflow',
workflowId: 'workflow',
file: 'file',
fileId: 'file',
folder: 'folder',
folderId: 'folder',
project: 'project',
projectId: 'project',
team: 'team',
teamId: 'team',
sheet: 'sheet',
sheetId: 'sheet',
document: 'document',
documentId: 'document',
knowledgeBase: 'knowledge base',
knowledgeBaseId: 'knowledge base',
server: 'server',
serverId: 'server',
tool: 'tool',
toolId: 'tool',
calendar: 'calendar',
calendarId: 'calendar',
label: 'label',
labelId: 'label',
site: 'site',
siteId: 'site',
collection: 'collection',
collectionId: 'collection',
item: 'item',
itemId: 'item',
contact: 'contact',
contactId: 'contact',
task: 'task',
taskId: 'task',
chat: 'chat',
chatId: 'chat',
}
return patterns[subBlockId] || 'value'
function getSemanticFallback(subBlockConfig: SubBlockConfig): string {
return (subBlockConfig.title ?? subBlockConfig.id).toLowerCase()
}
async function resolveCredential(credentialId: string, workflowId: string): Promise<string | null> {
@@ -150,26 +85,10 @@ async function resolveWorkflow(workflowId: string): Promise<string | null> {
async function resolveSelectorValue(
value: string,
selectorKey: SelectorKey,
extendedContext: ExtendedSelectorContext,
workflowId: string
selectorContext: SelectorContext
): Promise<string | null> {
try {
const definition = getSelectorDefinition(selectorKey)
const selectorContext = {
workflowId,
credentialId: extendedContext.credentialId,
domain: extendedContext.domain,
projectId: extendedContext.projectId,
planId: extendedContext.planId,
teamId: extendedContext.teamId,
knowledgeBaseId: extendedContext.knowledgeBaseId,
siteId: extendedContext.siteId,
collectionId: extendedContext.collectionId,
spreadsheetId: extendedContext.spreadsheetId,
baseId: extendedContext.baseId,
datasetId: extendedContext.datasetId,
serviceDeskId: extendedContext.serviceDeskId,
}
if (definition.fetchById) {
const result = await definition.fetchById({
@@ -219,37 +138,14 @@ export function formatValueForDisplay(value: unknown): string {
return String(value)
}
/**
* Extracts extended context from a block's subBlocks for selector resolution.
* This mirrors the context extraction done in the UI components.
*/
function extractExtendedContext(
function extractSelectorContext(
blockId: string,
currentState: WorkflowState
): ExtendedSelectorContext {
currentState: WorkflowState,
workflowId: string
): SelectorContext {
const block = currentState.blocks?.[blockId]
if (!block?.subBlocks) return {}
const getStringValue = (id: string): string | undefined => {
const subBlock = block.subBlocks[id] as { value?: unknown } | undefined
const val = subBlock?.value
return typeof val === 'string' ? val : undefined
}
return {
credentialId: getStringValue('credential'),
domain: getStringValue('domain'),
projectId: getStringValue('projectId'),
planId: getStringValue('planId'),
teamId: getStringValue('teamId'),
knowledgeBaseId: getStringValue('knowledgeBaseId'),
siteId: getStringValue('siteId'),
collectionId: getStringValue('collectionId'),
spreadsheetId: getStringValue('spreadsheetId') || getStringValue('fileId'),
baseId: getStringValue('baseId') || getStringValue('baseSelector'),
datasetId: getStringValue('datasetId') || getStringValue('datasetSelector'),
serviceDeskId: getStringValue('serviceDeskId') || getStringValue('serviceDeskSelector'),
}
if (!block?.subBlocks) return { workflowId }
return buildSelectorContextFromBlock(block.type, block.subBlocks, { workflowId })
}
/**
@@ -275,11 +171,14 @@ export async function resolveValueForDisplay(
const blockConfig = getBlock(context.blockType)
const subBlockConfig = blockConfig?.subBlocks.find((sb) => sb.id === context.subBlockId)
const semanticFallback = getSemanticFallback(context.subBlockId, subBlockConfig)
if (!subBlockConfig) {
return { original: value, displayLabel: formatValueForDisplay(value), resolved: false }
}
const semanticFallback = getSemanticFallback(subBlockConfig)
const extendedContext = context.blockId
? extractExtendedContext(context.blockId, context.currentState)
: {}
const selectorCtx = context.blockId
? extractSelectorContext(context.blockId, context.currentState, context.workflowId)
: { workflowId: context.workflowId }
// Credential fields (oauth-input or credential subBlockId)
const isCredentialField =
@@ -311,29 +210,10 @@ export async function resolveValueForDisplay(
// Selector types that require hydration (file-selector, sheet-selector, etc.)
// These support external service IDs like Google Drive file IDs
if (subBlockConfig && SELECTOR_TYPES_HYDRATION_REQUIRED.includes(subBlockConfig.type)) {
const resolution = resolveSelectorForSubBlock(subBlockConfig, {
workflowId: context.workflowId,
credentialId: extendedContext.credentialId,
domain: extendedContext.domain,
projectId: extendedContext.projectId,
planId: extendedContext.planId,
teamId: extendedContext.teamId,
knowledgeBaseId: extendedContext.knowledgeBaseId,
siteId: extendedContext.siteId,
collectionId: extendedContext.collectionId,
spreadsheetId: extendedContext.spreadsheetId,
baseId: extendedContext.baseId,
datasetId: extendedContext.datasetId,
serviceDeskId: extendedContext.serviceDeskId,
})
const resolution = resolveSelectorForSubBlock(subBlockConfig, selectorCtx)
if (resolution?.key) {
const label = await resolveSelectorValue(
value,
resolution.key,
extendedContext,
context.workflowId
)
const label = await resolveSelectorValue(value, resolution.key, selectorCtx)
if (label) {
return { original: value, displayLabel: label, resolved: true }
}

View File

@@ -0,0 +1,125 @@
/**
* @vitest-environment node
*/
import { describe, expect, it, vi } from 'vitest'
vi.unmock('@/blocks/registry')
import { getAllBlocks } from '@/blocks/registry'
import { buildSelectorContextFromBlock, SELECTOR_CONTEXT_FIELDS } from './context'
import { buildCanonicalIndex, isCanonicalPair } from './visibility'
describe('buildSelectorContextFromBlock', () => {
it('should extract knowledgeBaseId from knowledgeBaseSelector via canonical mapping', () => {
const ctx = buildSelectorContextFromBlock('knowledge', {
operation: { id: 'operation', type: 'dropdown', value: 'search' },
knowledgeBaseSelector: {
id: 'knowledgeBaseSelector',
type: 'knowledge-base-selector',
value: 'kb-uuid-123',
},
})
expect(ctx.knowledgeBaseId).toBe('kb-uuid-123')
})
it('should extract knowledgeBaseId from manualKnowledgeBaseId via canonical mapping', () => {
const ctx = buildSelectorContextFromBlock('knowledge', {
operation: { id: 'operation', type: 'dropdown', value: 'search' },
manualKnowledgeBaseId: {
id: 'manualKnowledgeBaseId',
type: 'short-input',
value: 'manual-kb-id',
},
})
expect(ctx.knowledgeBaseId).toBe('manual-kb-id')
})
it('should skip null/empty values', () => {
const ctx = buildSelectorContextFromBlock('knowledge', {
knowledgeBaseSelector: {
id: 'knowledgeBaseSelector',
type: 'knowledge-base-selector',
value: '',
},
})
expect(ctx.knowledgeBaseId).toBeUndefined()
})
it('should return empty context for unknown block types', () => {
const ctx = buildSelectorContextFromBlock('nonexistent_block', {
foo: { id: 'foo', type: 'short-input', value: 'bar' },
})
expect(ctx).toEqual({})
})
it('should pass through workflowId from opts', () => {
const ctx = buildSelectorContextFromBlock(
'knowledge',
{ operation: { id: 'operation', type: 'dropdown', value: 'search' } },
{ workflowId: 'wf-123' }
)
expect(ctx.workflowId).toBe('wf-123')
})
it('should ignore subblock keys not in SELECTOR_CONTEXT_FIELDS', () => {
const ctx = buildSelectorContextFromBlock('knowledge', {
operation: { id: 'operation', type: 'dropdown', value: 'search' },
query: { id: 'query', type: 'short-input', value: 'some search query' },
})
expect((ctx as Record<string, unknown>).query).toBeUndefined()
expect((ctx as Record<string, unknown>).operation).toBeUndefined()
})
})
describe('SELECTOR_CONTEXT_FIELDS validation', () => {
it('every entry must be a canonicalParamId (if a canonical pair exists) or a direct subblock ID', () => {
const allCanonicalParamIds = new Set<string>()
const allSubBlockIds = new Set<string>()
const idsInCanonicalPairs = new Set<string>()
for (const block of getAllBlocks()) {
const index = buildCanonicalIndex(block.subBlocks)
for (const sb of block.subBlocks) {
allSubBlockIds.add(sb.id)
if (sb.canonicalParamId) {
allCanonicalParamIds.add(sb.canonicalParamId)
}
}
for (const group of Object.values(index.groupsById)) {
if (!isCanonicalPair(group)) continue
if (group.basicId) idsInCanonicalPairs.add(group.basicId)
for (const advId of group.advancedIds) idsInCanonicalPairs.add(advId)
}
}
const errors: string[] = []
for (const field of SELECTOR_CONTEXT_FIELDS) {
const f = field as string
if (allCanonicalParamIds.has(f)) continue
if (idsInCanonicalPairs.has(f)) {
errors.push(
`"${f}" is a member subblock ID inside a canonical pair — use the canonicalParamId instead`
)
continue
}
if (!allSubBlockIds.has(f)) {
errors.push(`"${f}" is not a canonicalParamId or subblock ID in any block definition`)
}
}
if (errors.length > 0) {
throw new Error(`SELECTOR_CONTEXT_FIELDS validation failed:\n${errors.join('\n')}`)
}
})
})

View File

@@ -0,0 +1,60 @@
import { getBlock } from '@/blocks'
import type { SelectorContext } from '@/hooks/selectors/types'
import type { SubBlockState } from '@/stores/workflows/workflow/types'
import { buildCanonicalIndex } from './visibility'
/**
* Canonical param IDs (or raw subblock IDs) that correspond to SelectorContext fields.
* A subblock's resolved canonical key is set on the context only if it appears here.
*/
export const SELECTOR_CONTEXT_FIELDS = new Set<keyof SelectorContext>([
'oauthCredential',
'domain',
'teamId',
'projectId',
'knowledgeBaseId',
'planId',
'siteId',
'collectionId',
'spreadsheetId',
'fileId',
'baseId',
'datasetId',
'serviceDeskId',
])
/**
* Builds a SelectorContext from a block's subBlocks using the canonical index.
*
* Iterates all subblocks, resolves each through canonicalIdBySubBlockId to get
* the canonical key, then checks it against SELECTOR_CONTEXT_FIELDS.
* This avoids hardcoding subblock IDs and automatically handles basic/advanced
* renames.
*/
export function buildSelectorContextFromBlock(
blockType: string,
subBlocks: Record<string, SubBlockState | { value?: unknown }>,
opts?: { workflowId?: string }
): SelectorContext {
const context: SelectorContext = {}
if (opts?.workflowId) context.workflowId = opts.workflowId
const blockConfig = getBlock(blockType)
if (!blockConfig) return context
const canonicalIndex = buildCanonicalIndex(blockConfig.subBlocks)
for (const [subBlockId, subBlock] of Object.entries(subBlocks)) {
const val = subBlock?.value
if (val === null || val === undefined) continue
const strValue = typeof val === 'string' ? val : String(val)
if (!strValue) continue
const canonicalKey = canonicalIndex.canonicalIdBySubBlockId[subBlockId] ?? subBlockId
if (SELECTOR_CONTEXT_FIELDS.has(canonicalKey as keyof SelectorContext)) {
context[canonicalKey as keyof SelectorContext] = strValue
}
}
return context
}