Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/sim/app/api/auth/oauth/credentials/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { createLogger } from '@/lib/logs/console/logger'
import type { OAuthService } from '@/lib/oauth/oauth'
import { evaluateScopeCoverage, parseProvider } from '@/lib/oauth/oauth'
import { getUserEntityPermissions } from '@/lib/permissions/utils'
import { generateRequestId } from '@/lib/utils'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
import 'prismjs/components/prism-javascript'
import 'prismjs/themes/prism.css'

import {
isLikelyReferenceSegment,
SYSTEM_REFERENCE_PREFIXES,
splitReferenceSegment,
} from '@/lib/workflows/references'
import type { LoopType, ParallelType } from '@/lib/workflows/types'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { normalizeBlockName } from '@/stores/workflows/utils'

type IterationType = 'loop' | 'parallel'

Expand Down Expand Up @@ -130,6 +137,88 @@ export function IterationBadges({ nodeId, data, iterationType }: IterationBadges
collaborativeUpdateIterationCount,
collaborativeUpdateIterationCollection,
} = useCollaborativeWorkflow()
const accessiblePrefixes = useAccessibleReferencePrefixes(nodeId)

const shouldHighlightReference = useCallback(
(part: string): boolean => {
if (!part.startsWith('<') || !part.endsWith('>')) {
return false
}

if (!isLikelyReferenceSegment(part)) {
return false
}

const split = splitReferenceSegment(part)
if (!split) {
return false
}

const reference = split.reference

if (!accessiblePrefixes) {
return true
}

const inner = reference.slice(1, -1)
const [prefix] = inner.split('.')
const normalizedPrefix = normalizeBlockName(prefix)

if (SYSTEM_REFERENCE_PREFIXES.has(normalizedPrefix)) {
return true
}

return accessiblePrefixes.has(normalizedPrefix)
},
[accessiblePrefixes]
)

const highlightWithReferences = useCallback(
(code: string): string => {
const placeholders: Array<{
placeholder: string
original: string
type: 'var' | 'env'
}> = []

let processedCode = code

processedCode = processedCode.replace(/\{\{([^}]+)\}\}/g, (match) => {
const placeholder = `__ENV_VAR_${placeholders.length}__`
placeholders.push({ placeholder, original: match, type: 'env' })
return placeholder
})

processedCode = processedCode.replace(/<[^>]+>/g, (match) => {
if (shouldHighlightReference(match)) {
const placeholder = `__VAR_REF_${placeholders.length}__`
placeholders.push({ placeholder, original: match, type: 'var' })
return placeholder
}
return match
})

let highlightedCode = highlight(processedCode, languages.javascript, 'javascript')

placeholders.forEach(({ placeholder, original, type }) => {
if (type === 'env') {
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${original}</span>`
)
} else {
const escaped = original.replace(/</g, '&lt;').replace(/>/g, '&gt;')
highlightedCode = highlightedCode.replace(
placeholder,
`<span class="text-blue-500">${escaped}</span>`
)
}
})

return highlightedCode
},
[shouldHighlightReference]
)

// Handle type change
const handleTypeChange = useCallback(
Expand Down Expand Up @@ -325,7 +414,7 @@ export function IterationBadges({ nodeId, data, iterationType }: IterationBadges
<Editor
value={conditionString}
onValueChange={handleEditorChange}
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
highlight={highlightWithReferences}
padding={0}
style={{
fontFamily: 'monospace',
Expand Down Expand Up @@ -363,7 +452,7 @@ export function IterationBadges({ nodeId, data, iterationType }: IterationBadges
<Editor
value={editorValue}
onValueChange={handleEditorChange}
highlight={(code) => highlight(code, languages.javascript, 'javascript')}
highlight={highlightWithReferences}
padding={0}
style={{
fontFamily: 'monospace',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { CodeLanguage } from '@/lib/execution/languages'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { isLikelyReferenceSegment, SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/references'
import {
isLikelyReferenceSegment,
SYSTEM_REFERENCE_PREFIXES,
splitReferenceSegment,
} from '@/lib/workflows/references'
import { WandPromptBar } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/wand-prompt-bar/wand-prompt-bar'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
Expand Down Expand Up @@ -433,11 +437,18 @@ IMPORTANT FORMATTING RULES:
return false
}

const split = splitReferenceSegment(part)
if (!split) {
return false
}

const reference = split.reference

if (!accessiblePrefixes) {
return true
}

const inner = part.slice(1, -1)
const inner = reference.slice(1, -1)
const [prefix] = inner.split('.')
const normalizedPrefix = normalizeBlockName(prefix)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console/logger'
import { cn } from '@/lib/utils'
import { isLikelyReferenceSegment, SYSTEM_REFERENCE_PREFIXES } from '@/lib/workflows/references'
import {
isLikelyReferenceSegment,
SYSTEM_REFERENCE_PREFIXES,
splitReferenceSegment,
} from '@/lib/workflows/references'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/hooks/use-sub-block-value'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import { useTagSelection } from '@/hooks/use-tag-selection'
Expand Down Expand Up @@ -74,11 +78,18 @@ export function ConditionInput({
return false
}

const split = splitReferenceSegment(part)
if (!split) {
return false
}

const reference = split.reference

if (!accessiblePrefixes) {
return true
}

const inner = part.slice(1, -1)
const inner = reference.slice(1, -1)
const [prefix] = inner.split('.')
const normalizedPrefix = normalizeBlockName(prefix)

Expand Down
58 changes: 47 additions & 11 deletions apps/sim/components/ui/formatted-text.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import type { ReactNode } from 'react'
import { splitReferenceSegment } from '@/lib/workflows/references'
import { normalizeBlockName } from '@/stores/workflows/utils'

export interface HighlightContext {
Expand All @@ -17,16 +18,16 @@ const SYSTEM_PREFIXES = new Set(['start', 'loop', 'parallel', 'variable'])
export function formatDisplayText(text: string, context?: HighlightContext): ReactNode[] {
if (!text) return []

const shouldHighlightPart = (part: string): boolean => {
if (!part.startsWith('<') || !part.endsWith('>')) {
const shouldHighlightReference = (reference: string): boolean => {
if (!reference.startsWith('<') || !reference.endsWith('>')) {
return false
}

if (context?.highlightAll) {
return true
}

const inner = part.slice(1, -1)
const inner = reference.slice(1, -1)
const [prefix] = inner.split('.')
const normalizedPrefix = normalizeBlockName(prefix)

Expand All @@ -41,17 +42,52 @@ export function formatDisplayText(text: string, context?: HighlightContext): Rea
return false
}

const parts = text.split(/(<[^>]+>|\{\{[^}]+\}\})/g)
const nodes: ReactNode[] = []
const regex = /<[^>]+>|\{\{[^}]+\}\}/g
let lastIndex = 0
let key = 0

return parts.map((part, index) => {
if (shouldHighlightPart(part) || part.match(/^\{\{[^}]+\}\}$/)) {
return (
<span key={index} className='text-blue-500'>
{part}
const pushPlainText = (value: string) => {
if (!value) return
nodes.push(<span key={key++}>{value}</span>)
}

let match: RegExpExecArray | null
while ((match = regex.exec(text)) !== null) {
const matchText = match[0]
const index = match.index

if (index > lastIndex) {
pushPlainText(text.slice(lastIndex, index))
}

if (matchText.startsWith('{{')) {
nodes.push(
<span key={key++} className='text-blue-500'>
{matchText}
</span>
)
} else {
const split = splitReferenceSegment(matchText)

if (split && shouldHighlightReference(split.reference)) {
pushPlainText(split.leading)
nodes.push(
<span key={key++} className='text-blue-500'>
{split.reference}
</span>
)
} else {
nodes.push(<span key={key++}>{matchText}</span>)
}
}

return <span key={index}>{part}</span>
})
lastIndex = regex.lastIndex
}

if (lastIndex < text.length) {
pushPlainText(text.slice(lastIndex))
}

return nodes
}
9 changes: 3 additions & 6 deletions apps/sim/components/ui/tag-dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import { checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkTagTrigger, getTagSearchTerm } from '@/components/ui/tag-dropdown'
import { extractFieldsFromSchema, parseResponseFormatSafely } from '@/lib/response-format'
import type { BlockState } from '@/stores/workflows/workflow/types'
import { generateLoopBlocks } from '@/stores/workflows/workflow/utils'
Expand Down Expand Up @@ -1002,14 +1002,11 @@ describe('TagDropdown Search and Filtering', () => {
{ input: 'Hello <loop.in', cursorPosition: 14, expected: 'loop.in' },
{ input: 'Hello world', cursorPosition: 11, expected: '' },
{ input: 'Hello <var> and <loo', cursorPosition: 20, expected: 'loo' },
{ input: '<block.output> < <', cursorPosition: 18, expected: '' },
]

testCases.forEach(({ input, cursorPosition, expected }) => {
const textBeforeCursor = input.slice(0, cursorPosition)
const match = textBeforeCursor.match(/<([^>]*)$/)
const searchTerm = match ? match[1].toLowerCase() : ''

expect(searchTerm).toBe(expected)
expect(getTagSearchTerm(input, cursorPosition)).toBe(expected)
})
})

Expand Down
30 changes: 25 additions & 5 deletions apps/sim/components/ui/tag-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ export const checkTagTrigger = (text: string, cursorPosition: number): { show: b
return { show: false }
}

export const getTagSearchTerm = (text: string, cursorPosition: number): string => {
if (cursorPosition <= 0) {
return ''
}

const textBeforeCursor = text.slice(0, cursorPosition)
const lastOpenBracket = textBeforeCursor.lastIndexOf('<')

if (lastOpenBracket === -1) {
return ''
}

const lastCloseBracket = textBeforeCursor.lastIndexOf('>')

if (lastCloseBracket > lastOpenBracket) {
return ''
}

return textBeforeCursor.slice(lastOpenBracket + 1).toLowerCase()
}

const BLOCK_COLORS = {
VARIABLE: '#2F8BFF',
DEFAULT: '#2F55FF',
Expand Down Expand Up @@ -344,11 +365,10 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({
const getVariablesByWorkflowId = useVariablesStore((state) => state.getVariablesByWorkflowId)
const workflowVariables = workflowId ? getVariablesByWorkflowId(workflowId) : []

const searchTerm = useMemo(() => {
const textBeforeCursor = inputValue.slice(0, cursorPosition)
const match = textBeforeCursor.match(/<([^>]*)$/)
return match ? match[1].toLowerCase() : ''
}, [inputValue, cursorPosition])
const searchTerm = useMemo(
() => getTagSearchTerm(inputValue, cursorPosition),
[inputValue, cursorPosition]
)

const {
tags,
Expand Down
9 changes: 5 additions & 4 deletions apps/sim/lib/webhooks/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ export async function parseWebhookBody(
const requestClone = request.clone()
rawBody = await requestClone.text()

// Allow empty body - some webhooks send empty payloads
if (!rawBody || rawBody.length === 0) {
logger.warn(`[${requestId}] Rejecting request with empty body`)
return new NextResponse('Empty request body', { status: 400 })
logger.debug(`[${requestId}] Received request with empty body, treating as empty object`)
return { body: {}, rawBody: '' }
}
} catch (bodyError) {
logger.error(`[${requestId}] Failed to read request body`, {
Expand Down Expand Up @@ -96,9 +97,9 @@ export async function parseWebhookBody(
logger.debug(`[${requestId}] Parsed JSON webhook payload`)
}

// Allow empty JSON objects - some webhooks send empty payloads
if (Object.keys(body).length === 0) {
logger.warn(`[${requestId}] Rejecting empty JSON object`)
return new NextResponse('Empty JSON payload', { status: 400 })
logger.debug(`[${requestId}] Received empty JSON object`)
}
} catch (parseError) {
logger.error(`[${requestId}] Failed to parse webhook body`, {
Expand Down
Loading