Skip to content

Commit 33cf0de

Browse files
committed
improvement(grain): make trigger names in line with API since resource type not known
1 parent aad620c commit 33cf0de

File tree

10 files changed

+230
-26
lines changed

10 files changed

+230
-26
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1414
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1515

1616
/**
17-
* Dropdown option type - can be a simple string or an object with label, id, and optional icon
17+
* Dropdown option type - can be a simple string or an object with label, id, and optional icon.
18+
* Options with `hidden: true` are excluded from the picker but still resolve for label display,
19+
* so existing workflows that reference them continue to work.
1820
*/
1921
type DropdownOption =
2022
| string
21-
| { label: string; id: string; icon?: React.ComponentType<{ className?: string }> }
23+
| {
24+
label: string
25+
id: string
26+
icon?: React.ComponentType<{ className?: string }>
27+
hidden?: boolean
28+
}
2229

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

188-
const availableOptions = useMemo(() => {
195+
const allOptions = useMemo(() => {
189196
let opts: DropdownOption[] =
190197
fetchOptions && normalizedFetchedOptions.length > 0
191198
? normalizedFetchedOptions
192199
: evaluatedOptions
193200

194-
// Merge hydrated option if not already present
195201
if (hydratedOption) {
196202
const alreadyPresent = opts.some((o) =>
197203
typeof o === 'string' ? o === hydratedOption.id : o.id === hydratedOption.id
@@ -204,11 +210,12 @@ export const Dropdown = memo(function Dropdown({
204210
return opts
205211
}, [fetchOptions, normalizedFetchedOptions, evaluatedOptions, hydratedOption])
206212

207-
/**
208-
* Convert dropdown options to Combobox format
209-
*/
213+
const selectableOptions = useMemo(() => {
214+
return allOptions.filter((opt) => typeof opt === 'string' || !opt.hidden)
215+
}, [allOptions])
216+
210217
const comboboxOptions = useMemo((): ComboboxOption[] => {
211-
return availableOptions.map((opt) => {
218+
return selectableOptions.map((opt) => {
212219
if (typeof opt === 'string') {
213220
return { label: opt.toLowerCase(), value: opt }
214221
}
@@ -218,11 +225,16 @@ export const Dropdown = memo(function Dropdown({
218225
icon: 'icon' in opt ? opt.icon : undefined,
219226
}
220227
})
221-
}, [availableOptions])
228+
}, [selectableOptions])
222229

223230
const optionMap = useMemo(() => {
224-
return new Map(comboboxOptions.map((opt) => [opt.value, opt.label]))
225-
}, [comboboxOptions])
231+
return new Map(
232+
allOptions.map((opt) => {
233+
if (typeof opt === 'string') return [opt, opt.toLowerCase()]
234+
return [opt.id, opt.label.toLowerCase()]
235+
})
236+
)
237+
}, [allOptions])
226238

227239
const defaultOptionValue = useMemo(() => {
228240
if (multiSelect) return undefined

apps/sim/blocks/blocks/grain.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,15 +268,17 @@ Return ONLY the search term - no explanations, no quotes, no extra text.`,
268268
type: 'dropdown',
269269
mode: 'trigger',
270270
options: grainTriggerOptions,
271-
value: () => 'grain_webhook',
271+
value: () => 'grain_item_added',
272272
required: true,
273273
},
274+
...getTrigger('grain_item_added').subBlocks,
275+
...getTrigger('grain_item_updated').subBlocks,
276+
...getTrigger('grain_webhook').subBlocks,
274277
...getTrigger('grain_recording_created').subBlocks,
275278
...getTrigger('grain_recording_updated').subBlocks,
276279
...getTrigger('grain_highlight_created').subBlocks,
277280
...getTrigger('grain_highlight_updated').subBlocks,
278281
...getTrigger('grain_story_created').subBlocks,
279-
...getTrigger('grain_webhook').subBlocks,
280282
],
281283
tools: {
282284
access: [
@@ -447,12 +449,14 @@ Return ONLY the search term - no explanations, no quotes, no extra text.`,
447449
triggers: {
448450
enabled: true,
449451
available: [
452+
'grain_item_added',
453+
'grain_item_updated',
454+
'grain_webhook',
450455
'grain_recording_created',
451456
'grain_recording_updated',
452457
'grain_highlight_created',
453458
'grain_highlight_updated',
454459
'grain_story_created',
455-
'grain_webhook',
456460
],
457461
},
458462
}

apps/sim/blocks/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,14 @@ export interface SubBlockConfig {
233233
id: string
234234
icon?: React.ComponentType<{ className?: string }>
235235
group?: string
236+
hidden?: boolean
236237
}[]
237238
| (() => {
238239
label: string
239240
id: string
240241
icon?: React.ComponentType<{ className?: string }>
241242
group?: string
243+
hidden?: boolean
242244
}[])
243245
min?: number
244246
max?: number

apps/sim/lib/webhooks/provider-subscriptions.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,8 @@ export async function createGrainWebhookSubscription(
12581258
}
12591259

12601260
const actionMap: Record<string, Array<'added' | 'updated' | 'removed'>> = {
1261+
grain_item_added: ['added'],
1262+
grain_item_updated: ['updated'],
12611263
grain_recording_created: ['added'],
12621264
grain_recording_updated: ['updated'],
12631265
grain_highlight_created: ['added'],
@@ -1267,6 +1269,8 @@ export async function createGrainWebhookSubscription(
12671269

12681270
const eventTypeMap: Record<string, string[]> = {
12691271
grain_webhook: [],
1272+
grain_item_added: [],
1273+
grain_item_updated: [],
12701274
grain_recording_created: ['recording_added'],
12711275
grain_recording_updated: ['recording_updated'],
12721276
grain_highlight_created: ['highlight_added'],

apps/sim/triggers/grain/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export { grainHighlightCreatedTrigger } from './highlight_created'
22
export { grainHighlightUpdatedTrigger } from './highlight_updated'
3+
export { grainItemAddedTrigger } from './item_added'
4+
export { grainItemUpdatedTrigger } from './item_updated'
35
export { grainRecordingCreatedTrigger } from './recording_created'
46
export { grainRecordingUpdatedTrigger } from './recording_updated'
57
export { grainStoryCreatedTrigger } from './story_created'
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { GrainIcon } from '@/components/icons'
2+
import type { TriggerConfig } from '@/triggers/types'
3+
import { buildGenericOutputs, grainV2SetupInstructions } from './utils'
4+
5+
export const grainItemAddedTrigger: TriggerConfig = {
6+
id: 'grain_item_added',
7+
name: 'Grain Item Added',
8+
provider: 'grain',
9+
description: 'Trigger when a new item is added to a Grain view (recording, highlight, or story)',
10+
version: '1.0.0',
11+
icon: GrainIcon,
12+
13+
subBlocks: [
14+
{
15+
id: 'apiKey',
16+
title: 'API Key',
17+
type: 'short-input',
18+
placeholder: 'Enter your Grain API key (Personal Access Token)',
19+
description: 'Required to create the webhook in Grain.',
20+
password: true,
21+
required: true,
22+
mode: 'trigger',
23+
condition: {
24+
field: 'selectedTriggerId',
25+
value: 'grain_item_added',
26+
},
27+
},
28+
{
29+
id: 'viewId',
30+
title: 'View ID',
31+
type: 'short-input',
32+
placeholder: 'Enter Grain view UUID',
33+
description:
34+
'The view determines which content type fires events (recordings, highlights, or stories).',
35+
required: true,
36+
mode: 'trigger',
37+
condition: {
38+
field: 'selectedTriggerId',
39+
value: 'grain_item_added',
40+
},
41+
},
42+
{
43+
id: 'triggerSave',
44+
title: '',
45+
type: 'trigger-save',
46+
hideFromPreview: true,
47+
mode: 'trigger',
48+
triggerId: 'grain_item_added',
49+
condition: {
50+
field: 'selectedTriggerId',
51+
value: 'grain_item_added',
52+
},
53+
},
54+
{
55+
id: 'triggerInstructions',
56+
title: 'Setup Instructions',
57+
hideFromPreview: true,
58+
type: 'text',
59+
defaultValue: grainV2SetupInstructions('item added'),
60+
mode: 'trigger',
61+
condition: {
62+
field: 'selectedTriggerId',
63+
value: 'grain_item_added',
64+
},
65+
},
66+
],
67+
68+
outputs: buildGenericOutputs(),
69+
70+
webhook: {
71+
method: 'POST',
72+
headers: {
73+
'Content-Type': 'application/json',
74+
},
75+
},
76+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { GrainIcon } from '@/components/icons'
2+
import type { TriggerConfig } from '@/triggers/types'
3+
import { buildGenericOutputs, grainV2SetupInstructions } from './utils'
4+
5+
export const grainItemUpdatedTrigger: TriggerConfig = {
6+
id: 'grain_item_updated',
7+
name: 'Grain Item Updated',
8+
provider: 'grain',
9+
description: 'Trigger when an item is updated in a Grain view (recording, highlight, or story)',
10+
version: '1.0.0',
11+
icon: GrainIcon,
12+
13+
subBlocks: [
14+
{
15+
id: 'apiKey',
16+
title: 'API Key',
17+
type: 'short-input',
18+
placeholder: 'Enter your Grain API key (Personal Access Token)',
19+
description: 'Required to create the webhook in Grain.',
20+
password: true,
21+
required: true,
22+
mode: 'trigger',
23+
condition: {
24+
field: 'selectedTriggerId',
25+
value: 'grain_item_updated',
26+
},
27+
},
28+
{
29+
id: 'viewId',
30+
title: 'View ID',
31+
type: 'short-input',
32+
placeholder: 'Enter Grain view UUID',
33+
description:
34+
'The view determines which content type fires events (recordings, highlights, or stories).',
35+
required: true,
36+
mode: 'trigger',
37+
condition: {
38+
field: 'selectedTriggerId',
39+
value: 'grain_item_updated',
40+
},
41+
},
42+
{
43+
id: 'triggerSave',
44+
title: '',
45+
type: 'trigger-save',
46+
hideFromPreview: true,
47+
mode: 'trigger',
48+
triggerId: 'grain_item_updated',
49+
condition: {
50+
field: 'selectedTriggerId',
51+
value: 'grain_item_updated',
52+
},
53+
},
54+
{
55+
id: 'triggerInstructions',
56+
title: 'Setup Instructions',
57+
hideFromPreview: true,
58+
type: 'text',
59+
defaultValue: grainV2SetupInstructions('item updated'),
60+
mode: 'trigger',
61+
condition: {
62+
field: 'selectedTriggerId',
63+
value: 'grain_item_updated',
64+
},
65+
},
66+
],
67+
68+
outputs: buildGenericOutputs(),
69+
70+
webhook: {
71+
method: 'POST',
72+
headers: {
73+
'Content-Type': 'application/json',
74+
},
75+
},
76+
}

apps/sim/triggers/grain/utils.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import type { TriggerOutput } from '@/triggers/types'
22

33
/**
4-
* Shared trigger dropdown options for all Grain triggers
4+
* Trigger dropdown options for Grain triggers.
5+
* New options (Item Added / Item Updated / All Events) correctly scope by view_id only.
6+
* Legacy options are hidden from the picker but still resolve for existing workflows.
57
*/
68
export const grainTriggerOptions = [
7-
{ label: 'General Webhook (All Events)', id: 'grain_webhook' },
8-
{ label: 'Recording Created', id: 'grain_recording_created' },
9-
{ label: 'Recording Updated', id: 'grain_recording_updated' },
10-
{ label: 'Highlight Created', id: 'grain_highlight_created' },
11-
{ label: 'Highlight Updated', id: 'grain_highlight_updated' },
12-
{ label: 'Story Created', id: 'grain_story_created' },
9+
{ label: 'Item Added', id: 'grain_item_added' },
10+
{ label: 'Item Updated', id: 'grain_item_updated' },
11+
{ label: 'All Events', id: 'grain_webhook' },
12+
{ label: 'Recording Created', id: 'grain_recording_created', hidden: true },
13+
{ label: 'Recording Updated', id: 'grain_recording_updated', hidden: true },
14+
{ label: 'Highlight Created', id: 'grain_highlight_created', hidden: true },
15+
{ label: 'Highlight Updated', id: 'grain_highlight_updated', hidden: true },
16+
{ label: 'Story Created', id: 'grain_story_created', hidden: true },
1317
]
1418

1519
/**
@@ -32,6 +36,25 @@ export function grainSetupInstructions(eventType: string): string {
3236
.join('')
3337
}
3438

39+
/**
40+
* Setup instructions for the v2 triggers that correctly explain view-based scoping.
41+
*/
42+
export function grainV2SetupInstructions(action: string): string {
43+
const instructions = [
44+
'Enter your Grain API Key (Personal Access Token). You can find or create one in Grain at <strong>Workspace Settings &gt; API</strong> under Integrations on <a href="https://grain.com/app/settings/integrations?tab=api" target="_blank" rel="noopener noreferrer">grain.com</a>.',
45+
`Enter a Grain <strong>view ID</strong>. Each view has a type &mdash; <em>recordings</em>, <em>highlights</em>, or <em>stories</em> &mdash; and only items matching that type will fire the <strong>${action}</strong> event.`,
46+
'To find your view IDs, use the <strong>List Views</strong> operation on this block or call <code>GET /_/public-api/views</code> directly.',
47+
'The webhook is created automatically when you save and will be deleted when you remove this trigger.',
48+
]
49+
50+
return instructions
51+
.map(
52+
(instruction, index) =>
53+
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
54+
)
55+
.join('')
56+
}
57+
3558
/**
3659
* Build output schema for recording events
3760
* Webhook payload structure: { type, user_id, data: { ...recording } }

apps/sim/triggers/grain/webhook.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { GrainIcon } from '@/components/icons'
22
import type { TriggerConfig } from '@/triggers/types'
3-
import { buildGenericOutputs, grainSetupInstructions } from './utils'
3+
import { buildGenericOutputs, grainV2SetupInstructions } from './utils'
44

55
export const grainWebhookTrigger: TriggerConfig = {
66
id: 'grain_webhook',
7-
name: 'Grain Webhook',
7+
name: 'Grain All Events',
88
provider: 'grain',
9-
description: 'Generic webhook trigger for all actions in a selected Grain view',
9+
description: 'Trigger on all actions (added, updated, removed) in a Grain view',
1010
version: '1.0.0',
1111
icon: GrainIcon,
1212

@@ -30,7 +30,8 @@ export const grainWebhookTrigger: TriggerConfig = {
3030
title: 'View ID',
3131
type: 'short-input',
3232
placeholder: 'Enter Grain view UUID',
33-
description: 'Required by Grain to create the webhook subscription.',
33+
description:
34+
'The view determines which content type fires events (recordings, highlights, or stories).',
3435
required: true,
3536
mode: 'trigger',
3637
condition: {
@@ -55,7 +56,7 @@ export const grainWebhookTrigger: TriggerConfig = {
5556
title: 'Setup Instructions',
5657
hideFromPreview: true,
5758
type: 'text',
58-
defaultValue: grainSetupInstructions('All events'),
59+
defaultValue: grainV2SetupInstructions('all'),
5960
mode: 'trigger',
6061
condition: {
6162
field: 'selectedTriggerId',

0 commit comments

Comments
 (0)