Skip to content

Commit 9034dc1

Browse files
author
waleed
committed
remove unused subblock store op, update search modal to reflect list of triggers
1 parent e8897a7 commit 9034dc1

File tree

24 files changed

+297
-277
lines changed

24 files changed

+297
-277
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/text.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function Text({ blockId, subBlockId, content, className }: TextProps) {
1515
className={`rounded-md border bg-card p-4 shadow-sm ${className || ''}`}
1616
>
1717
<div
18-
className='prose prose-sm dark:prose-invert max-w-none text-sm [&_a]:text-primary [&_a]:underline [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs [&_strong]:font-semibold [&_ul]:ml-5 [&_ul]:list-disc'
18+
className='prose prose-sm dark:prose-invert max-w-none text-sm [&_a]:text-blue-600 [&_a]:underline [&_a]:hover:text-blue-700 [&_a]:dark:text-blue-400 [&_a]:dark:hover:text-blue-300 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs [&_strong]:font-semibold [&_ul]:ml-5 [&_ul]:list-disc'
1919
dangerouslySetInnerHTML={{ __html: content }}
2020
/>
2121
</div>

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-save/trigger-save.tsx

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2-
import { AlertCircle, Check, Save, Trash2 } from 'lucide-react'
2+
import { AlertCircle, Check, Copy, Save, Trash2 } from 'lucide-react'
33
import { Alert, AlertDescription } from '@/components/ui/alert'
44
import {
55
AlertDialog,
@@ -12,6 +12,7 @@ import {
1212
AlertDialogTitle,
1313
} from '@/components/ui/alert-dialog'
1414
import { Button } from '@/components/ui/button'
15+
import { Input } from '@/components/ui/input'
1516
import { createLogger } from '@/lib/logs/console/logger'
1617
import { cn } from '@/lib/utils'
1718
import { useTriggerConfigAggregation } from '@/hooks/use-trigger-config-aggregation'
@@ -42,6 +43,10 @@ export function TriggerSave({
4243
const [errorMessage, setErrorMessage] = useState<string | null>(null)
4344
const [deleteStatus, setDeleteStatus] = useState<'idle' | 'deleting'>('idle')
4445
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
46+
const [testUrl, setTestUrl] = useState<string | null>(null)
47+
const [testUrlExpiresAt, setTestUrlExpiresAt] = useState<string | null>(null)
48+
const [isGeneratingTestUrl, setIsGeneratingTestUrl] = useState(false)
49+
const [copied, setCopied] = useState<string | null>(null)
4550

4651
const { webhookId, saveConfig, deleteConfig, isLoading } = useWebhookManagement({
4752
blockId,
@@ -56,6 +61,10 @@ export function TriggerSave({
5661

5762
const triggerDef = triggerId && isTriggerValid(triggerId) ? getTrigger(triggerId) : null
5863

64+
// Check if trigger supports webhook URLs (has webhookUrlDisplay subblock)
65+
const hasWebhookUrlDisplay =
66+
triggerDef?.subBlocks.some((sb) => sb.id === 'webhookUrlDisplay') ?? false
67+
5968
const validateRequiredFields = useCallback(
6069
(
6170
configToCheck: Record<string, any> | null | undefined
@@ -225,6 +234,38 @@ export function TriggerSave({
225234
}
226235
}
227236

237+
const generateTestUrl = async () => {
238+
if (!webhookId) return
239+
try {
240+
setIsGeneratingTestUrl(true)
241+
const res = await fetch(`/api/webhooks/${webhookId}/test-url`, {
242+
method: 'POST',
243+
headers: { 'Content-Type': 'application/json' },
244+
body: JSON.stringify({}),
245+
})
246+
if (!res.ok) {
247+
const err = await res.json().catch(() => ({}))
248+
throw new Error(err?.error || 'Failed to generate test URL')
249+
}
250+
const json = await res.json()
251+
setTestUrl(json.url)
252+
setTestUrlExpiresAt(json.expiresAt)
253+
} catch (e) {
254+
logger.error('Failed to generate test webhook URL', { error: e })
255+
setErrorMessage(
256+
e instanceof Error ? e.message : 'Failed to generate test URL. Please try again.'
257+
)
258+
} finally {
259+
setIsGeneratingTestUrl(false)
260+
}
261+
}
262+
263+
const copyToClipboard = (text: string, type: string): void => {
264+
navigator.clipboard.writeText(text)
265+
setCopied(type)
266+
setTimeout(() => setCopied(null), 2000)
267+
}
268+
228269
const handleDeleteClick = () => {
229270
if (isPreview || disabled || !webhookId) return
230271
setShowDeleteDialog(true)
@@ -242,6 +283,8 @@ export function TriggerSave({
242283
setDeleteStatus('idle')
243284
setSaveStatus('idle')
244285
setErrorMessage(null)
286+
setTestUrl(null)
287+
setTestUrlExpiresAt(null)
245288

246289
logger.info('Trigger configuration deleted successfully', {
247290
blockId,
@@ -325,6 +368,67 @@ export function TriggerSave({
325368
</Alert>
326369
)}
327370

371+
{webhookId && hasWebhookUrlDisplay && (
372+
<div className='mt-2 space-y-1'>
373+
<div className='flex items-center justify-between'>
374+
<span className='font-medium text-sm'>Test Webhook URL</span>
375+
<Button
376+
variant='outline'
377+
size='sm'
378+
onClick={generateTestUrl}
379+
disabled={isGeneratingTestUrl || isProcessing}
380+
className='h-8 rounded-[8px]'
381+
>
382+
{isGeneratingTestUrl ? (
383+
<>
384+
<div className='mr-2 h-3 w-3 animate-spin rounded-full border-[1.5px] border-current border-t-transparent' />
385+
Generating…
386+
</>
387+
) : testUrl ? (
388+
'Regenerate'
389+
) : (
390+
'Generate'
391+
)}
392+
</Button>
393+
</div>
394+
{testUrl ? (
395+
<div className='flex items-center gap-2'>
396+
<Input
397+
readOnly
398+
value={testUrl}
399+
className='h-9 flex-1 rounded-[8px] font-mono text-xs'
400+
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
401+
(e.target as HTMLInputElement).select()
402+
}
403+
/>
404+
<Button
405+
type='button'
406+
size='icon'
407+
variant='outline'
408+
className='h-9 w-9 rounded-[8px]'
409+
onClick={() => copyToClipboard(testUrl, 'testUrl')}
410+
>
411+
{copied === 'testUrl' ? (
412+
<Check className='h-4 w-4 text-green-500' />
413+
) : (
414+
<Copy className='h-4 w-4' />
415+
)}
416+
</Button>
417+
</div>
418+
) : (
419+
<p className='text-muted-foreground text-xs'>
420+
Generate a temporary URL that executes this webhook against the live (un-deployed)
421+
workflow state.
422+
</p>
423+
)}
424+
{testUrlExpiresAt && (
425+
<p className='text-muted-foreground text-xs'>
426+
Expires at {new Date(testUrlExpiresAt).toLocaleString()}
427+
</p>
428+
)}
429+
</div>
430+
)}
431+
328432
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
329433
<AlertDialogContent>
330434
<AlertDialogHeader>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,7 @@ export const WorkflowBlock = memo(
230230

231231
// Check if this is a starter block or trigger block
232232
const isStarterBlock = type === 'starter'
233-
const isTriggerBlock = config.category === 'triggers'
234-
const isWebhookTriggerBlock = type === 'webhook'
233+
const isWebhookTriggerBlock = type === 'webhook' || type === 'generic_webhook'
235234

236235
const reactivateSchedule = async (scheduleId: string) => {
237236
try {

apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { Dialog, DialogOverlay, DialogPortal, DialogTitle } from '@/components/u
1919
import { Input } from '@/components/ui/input'
2020
import { useBrandConfig } from '@/lib/branding/branding'
2121
import { cn } from '@/lib/utils'
22+
import { getTriggersForSidebar, hasTriggerCapability } from '@/lib/workflows/trigger-utils'
2223
import { getKeyboardShortcutText } from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts'
2324
import { getAllBlocks } from '@/blocks'
2425
import { type NavigationSection, useSearchNavigation } from './hooks/use-search-navigation'
@@ -54,6 +55,7 @@ interface BlockItem {
5455
icon: React.ComponentType<any>
5556
bgColor: string
5657
type: string
58+
config?: any // Store block config to check trigger capability
5759
}
5860

5961
interface ToolItem {
@@ -147,16 +149,13 @@ export function SearchModal({
147149
return [...regularBlocks, ...specialBlocks].sort((a, b) => a.name.localeCompare(b.name))
148150
}, [isOnWorkflowPage])
149151

150-
// Get all available triggers - only when on workflow page
151152
const triggers = useMemo(() => {
152153
if (!isOnWorkflowPage) return []
153154

154-
const allBlocks = getAllBlocks()
155-
return allBlocks
156-
.filter(
157-
(block) =>
158-
block.type !== 'starter' && !block.hideFromToolbar && block.category === 'triggers'
159-
)
155+
const triggerBlocks = getTriggersForSidebar()
156+
157+
return triggerBlocks
158+
.filter((block) => block.type !== 'webhook') // Exclude old webhook block - use generic_webhook instead
160159
.map(
161160
(block): BlockItem => ({
162161
id: block.type,
@@ -166,6 +165,7 @@ export function SearchModal({
166165
icon: block.icon,
167166
bgColor: block.bgColor || '#6B7280',
168167
type: block.type,
168+
config: block, // Store config to check trigger capability
169169
})
170170
)
171171
.sort((a, b) => a.name.localeCompare(b.name))
@@ -371,11 +371,12 @@ export function SearchModal({
371371

372372
// Handle block/tool click (same as toolbar interaction)
373373
const handleBlockClick = useCallback(
374-
(blockType: string) => {
374+
(blockType: string, enableTriggerMode?: boolean) => {
375375
// Dispatch a custom event to be caught by the workflow component
376376
const event = new CustomEvent('add-block-from-toolbar', {
377377
detail: {
378378
type: blockType,
379+
enableTriggerMode: enableTriggerMode || false,
379380
},
380381
})
381382
window.dispatchEvent(event)
@@ -475,7 +476,9 @@ export function SearchModal({
475476
const { section, item } = current
476477

477478
if (section.id === 'blocks' || section.id === 'triggers' || section.id === 'tools') {
478-
handleBlockClick(item.type)
479+
const enableTriggerMode =
480+
section.id === 'triggers' && item.config ? hasTriggerCapability(item.config) : false
481+
handleBlockClick(item.type, enableTriggerMode)
479482
} else if (section.id === 'list') {
480483
switch (item.type) {
481484
case 'workspace':
@@ -652,7 +655,12 @@ export function SearchModal({
652655
{filteredTriggers.map((trigger, index) => (
653656
<button
654657
key={trigger.id}
655-
onClick={() => handleBlockClick(trigger.type)}
658+
onClick={() =>
659+
handleBlockClick(
660+
trigger.type,
661+
trigger.config ? hasTriggerCapability(trigger.config) : false
662+
)
663+
}
656664
data-nav-item={`triggers-${index}`}
657665
className={`flex h-auto w-[180px] flex-shrink-0 cursor-pointer flex-col items-start gap-2 rounded-[8px] border p-3 transition-all duration-200 ${
658666
isItemSelected('triggers', index)

apps/sim/blocks/blocks/generic_webhook.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,7 @@ export const GenericWebhookBlock: BlockConfig = {
1919
- Continuing example above, the body can be accessed in downstream block using dot notation. E.g. <webhook1.message> and <webhook1.data.key>
2020
- Only use when there's no existing integration for the service with triggerAllowed flag set to true.
2121
`,
22-
subBlocks: [
23-
// Generic webhook configuration - always visible
24-
...getTrigger('generic_webhook').subBlocks,
25-
// Optional input format for structured data including files
26-
{
27-
id: 'inputFormat',
28-
title: 'Input Format',
29-
type: 'input-format',
30-
layout: 'full',
31-
description:
32-
'Define the expected JSON input schema for this webhook (optional). Use type "files" for file uploads.',
33-
},
34-
],
22+
subBlocks: [...getTrigger('generic_webhook').subBlocks],
3523

3624
tools: {
3725
access: [], // No external tools needed for triggers

0 commit comments

Comments
 (0)