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
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'

/**
* Dropdown option type - can be a simple string or an object with label, id, and optional icon
* Dropdown option type - can be a simple string or an object with label, id, and optional icon.
* Options with `hidden: true` are excluded from the picker but still resolve for label display,
* so existing workflows that reference them continue to work.
*/
type DropdownOption =
| string
| { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
| {
label: string
id: string
icon?: React.ComponentType<{ className?: string }>
hidden?: boolean
}

/**
* Props for the Dropdown component
Expand Down Expand Up @@ -185,13 +192,12 @@ export const Dropdown = memo(function Dropdown({
return fetchedOptions.map((opt) => ({ label: opt.label, id: opt.id }))
}, [fetchedOptions])

const availableOptions = useMemo(() => {
const allOptions = useMemo(() => {
let opts: DropdownOption[] =
fetchOptions && normalizedFetchedOptions.length > 0
? normalizedFetchedOptions
: evaluatedOptions

// Merge hydrated option if not already present
if (hydratedOption) {
const alreadyPresent = opts.some((o) =>
typeof o === 'string' ? o === hydratedOption.id : o.id === hydratedOption.id
Expand All @@ -204,21 +210,19 @@ export const Dropdown = memo(function Dropdown({
return opts
}, [fetchOptions, normalizedFetchedOptions, evaluatedOptions, hydratedOption])

/**
* Convert dropdown options to Combobox format
*/
const comboboxOptions = useMemo((): ComboboxOption[] => {
return availableOptions.map((opt) => {
return allOptions.map((opt) => {
if (typeof opt === 'string') {
return { label: opt.toLowerCase(), value: opt }
}
return {
label: opt.label.toLowerCase(),
value: opt.id,
icon: 'icon' in opt ? opt.icon : undefined,
hidden: opt.hidden,
}
})
}, [availableOptions])
}, [allOptions])

const optionMap = useMemo(() => {
return new Map(comboboxOptions.map((opt) => [opt.value, opt.label]))
Expand Down
10 changes: 7 additions & 3 deletions apps/sim/blocks/blocks/grain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,15 +268,17 @@ Return ONLY the search term - no explanations, no quotes, no extra text.`,
type: 'dropdown',
mode: 'trigger',
options: grainTriggerOptions,
value: () => 'grain_webhook',
value: () => 'grain_item_added',
required: true,
},
...getTrigger('grain_item_added').subBlocks,
...getTrigger('grain_item_updated').subBlocks,
...getTrigger('grain_webhook').subBlocks,
...getTrigger('grain_recording_created').subBlocks,
...getTrigger('grain_recording_updated').subBlocks,
...getTrigger('grain_highlight_created').subBlocks,
...getTrigger('grain_highlight_updated').subBlocks,
...getTrigger('grain_story_created').subBlocks,
...getTrigger('grain_webhook').subBlocks,
],
tools: {
access: [
Expand Down Expand Up @@ -447,12 +449,14 @@ Return ONLY the search term - no explanations, no quotes, no extra text.`,
triggers: {
enabled: true,
available: [
'grain_item_added',
'grain_item_updated',
'grain_webhook',
'grain_recording_created',
'grain_recording_updated',
'grain_highlight_created',
'grain_highlight_updated',
'grain_story_created',
'grain_webhook',
],
},
}
2 changes: 2 additions & 0 deletions apps/sim/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ export interface SubBlockConfig {
id: string
icon?: React.ComponentType<{ className?: string }>
group?: string
hidden?: boolean
}[]
| (() => {
label: string
id: string
icon?: React.ComponentType<{ className?: string }>
group?: string
hidden?: boolean
}[])
min?: number
max?: number
Expand Down
20 changes: 14 additions & 6 deletions apps/sim/components/emcn/components/combobox/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const comboboxVariants = cva(
export type ComboboxOption = {
label: string
value: string
/** When true, hidden from the picker list but still resolves for display */
hidden?: boolean
/** Icon component to render */
icon?: React.ComponentType<{ className?: string }>
/** Pre-rendered icon element (alternative to icon component) */
Expand Down Expand Up @@ -207,12 +209,11 @@ const Combobox = memo(
* Filter options based on current value or search query
*/
const filteredOptions = useMemo(() => {
let result = allOptions
let result = allOptions.filter((opt) => !opt.hidden)

// Filter by editable input value
if (filterOptions && value && open) {
const currentValue = value.toString().toLowerCase()
const exactMatch = allOptions.find(
const exactMatch = result.find(
(opt) => opt.value === value || opt.label.toLowerCase() === currentValue
)
if (!exactMatch) {
Expand All @@ -224,7 +225,6 @@ const Combobox = memo(
}
}

// Filter by search query (for searchable mode)
if (searchable && searchQuery) {
const query = searchQuery.toLowerCase()
result = result.filter((option) => {
Expand All @@ -242,10 +242,18 @@ const Combobox = memo(
*/
const filteredGroups = useMemo(() => {
if (!groups) return null
if (!searchable || !searchQuery) return groups

const baseGroups = groups
.map((group) => ({
...group,
items: group.items.filter((opt) => !opt.hidden),
}))
.filter((group) => group.items.length > 0)

if (!searchable || !searchQuery) return baseGroups

const query = searchQuery.toLowerCase()
return groups
return baseGroups
.map((group) => ({
...group,
items: group.items.filter((option) => {
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/lib/webhooks/provider-subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,8 @@ export async function createGrainWebhookSubscription(
}

const actionMap: Record<string, Array<'added' | 'updated' | 'removed'>> = {
grain_item_added: ['added'],
grain_item_updated: ['updated'],
grain_recording_created: ['added'],
grain_recording_updated: ['updated'],
grain_highlight_created: ['added'],
Expand All @@ -1267,6 +1269,8 @@ export async function createGrainWebhookSubscription(

const eventTypeMap: Record<string, string[]> = {
grain_webhook: [],
grain_item_added: [],
grain_item_updated: [],
grain_recording_created: ['recording_added'],
grain_recording_updated: ['recording_updated'],
grain_highlight_created: ['highlight_added'],
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/triggers/grain/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { grainHighlightCreatedTrigger } from './highlight_created'
export { grainHighlightUpdatedTrigger } from './highlight_updated'
export { grainItemAddedTrigger } from './item_added'
export { grainItemUpdatedTrigger } from './item_updated'
export { grainRecordingCreatedTrigger } from './recording_created'
export { grainRecordingUpdatedTrigger } from './recording_updated'
export { grainStoryCreatedTrigger } from './story_created'
Expand Down
76 changes: 76 additions & 0 deletions apps/sim/triggers/grain/item_added.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { GrainIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
import { buildGenericOutputs, grainV2SetupInstructions } from './utils'

export const grainItemAddedTrigger: TriggerConfig = {
id: 'grain_item_added',
name: 'Grain Item Added',
provider: 'grain',
description: 'Trigger when a new item is added to a Grain view (recording, highlight, or story)',
version: '1.0.0',
icon: GrainIcon,

subBlocks: [
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Grain API key (Personal Access Token)',
description: 'Required to create the webhook in Grain.',
password: true,
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_added',
},
},
{
id: 'viewId',
title: 'View ID',
type: 'short-input',
placeholder: 'Enter Grain view UUID',
description:
'The view determines which content type fires events (recordings, highlights, or stories).',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_added',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'grain_item_added',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_added',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
hideFromPreview: true,
type: 'text',
defaultValue: grainV2SetupInstructions('item added'),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_added',
},
},
],

outputs: buildGenericOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
76 changes: 76 additions & 0 deletions apps/sim/triggers/grain/item_updated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { GrainIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
import { buildGenericOutputs, grainV2SetupInstructions } from './utils'

export const grainItemUpdatedTrigger: TriggerConfig = {
id: 'grain_item_updated',
name: 'Grain Item Updated',
provider: 'grain',
description: 'Trigger when an item is updated in a Grain view (recording, highlight, or story)',
version: '1.0.0',
icon: GrainIcon,

subBlocks: [
{
id: 'apiKey',
title: 'API Key',
type: 'short-input',
placeholder: 'Enter your Grain API key (Personal Access Token)',
description: 'Required to create the webhook in Grain.',
password: true,
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_updated',
},
},
{
id: 'viewId',
title: 'View ID',
type: 'short-input',
placeholder: 'Enter Grain view UUID',
description:
'The view determines which content type fires events (recordings, highlights, or stories).',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_updated',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
hideFromPreview: true,
mode: 'trigger',
triggerId: 'grain_item_updated',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_updated',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
hideFromPreview: true,
type: 'text',
defaultValue: grainV2SetupInstructions('item updated'),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'grain_item_updated',
},
},
],

outputs: buildGenericOutputs(),

webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
}
Loading
Loading