Implement triggerField helper across all trigger modules

- Standardize virtual field creation using triggerField helper
- Simplify field definitions by removing repetitive virtual field boilerplate
- Use consistent naming pattern: '__trigger_' + fieldName instead of '__builtin_'
- Preserve existing field conditions while adding parameter storage logic
- Update all trigger field modules to use Field type consistently
This commit is contained in:
2025-09-10 13:35:48 +02:00
parent 8f0ee4bcef
commit 9a3b94ef60
6 changed files with 91 additions and 139 deletions

View File

@@ -1,62 +1,27 @@
import type {Field} from 'payload'
import type { WorkflowsPluginConfig } from '../plugin/config-types.js'
import {triggerField} from "./helpers.js"
export function getCollectionTriggerFields<T extends string>(
collectionTriggers: WorkflowsPluginConfig<T>['collectionTriggers']
): Field[] {
return [
{
name: '__builtin_collectionSlug',
triggerField({
name: 'collectionSlug',
type: 'select',
admin: {
condition: (_, siblingData) => siblingData?.type === 'collection-trigger',
description: 'Collection that triggers the workflow',
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.collectionSlug || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.collectionSlug = value
return undefined // Virtual field, don't store directly
}
]
},
options: Object.keys(collectionTriggers || {}),
virtual: true,
},
{
name: '__builtin_operation',
}),
triggerField({
name: 'operation',
type: 'select',
admin: {
condition: (_, siblingData) => siblingData?.type === 'collection-trigger',
description: 'Collection operation that triggers the workflow',
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.operation || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.operation = value
return undefined // Virtual field, don't store directly
}
]
},
options: [
'create',
'delete',
'read',
'update',
],
virtual: true,
}
})
]
}

View File

@@ -1,29 +1,17 @@
import type {Field} from 'payload'
import {triggerField} from "./helpers.js"
export function getCronTriggerFields(): Field[] {
return [
{
name: '__builtin_cronExpression',
triggerField({
name: 'cronExpression',
type: 'text',
admin: {
condition: (_, siblingData) => siblingData?.type === 'cron-trigger',
description: 'Cron expression for scheduled execution (e.g., "0 0 * * *" for daily at midnight)',
placeholder: '0 0 * * *'
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.cronExpression || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.cronExpression = value
return undefined // Virtual field, don't store directly
}
]
},
validate: (value: any, {siblingData}: any) => {
const cronValue = value || siblingData?.parameters?.cronExpression
if (siblingData?.type === 'cron-trigger' && !cronValue) {
@@ -44,10 +32,9 @@ export function getCronTriggerFields(): Field[] {
return true
},
virtual: true,
},
{
name: '__builtin_timezone',
}),
triggerField({
name: 'timezone',
type: 'text',
admin: {
condition: (_, siblingData) => siblingData?.type === 'cron-trigger',
@@ -55,20 +42,6 @@ export function getCronTriggerFields(): Field[] {
placeholder: 'UTC'
},
defaultValue: 'UTC',
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.timezone || 'UTC'
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.timezone = value || 'UTC'
return undefined // Virtual field, don't store directly
}
]
},
validate: (value: any, {siblingData}: any) => {
const tzValue = value || siblingData?.parameters?.timezone
if (siblingData?.type === 'cron-trigger' && tzValue) {
@@ -82,7 +55,6 @@ export function getCronTriggerFields(): Field[] {
}
return true
},
virtual: true,
}
})
]
}

View File

@@ -1,56 +1,28 @@
import type {Field} from 'payload'
import {triggerField} from "./helpers.js"
export function getGlobalTriggerFields(): Field[] {
return [
{
name: '__builtin_global',
triggerField({
name: 'global',
type: 'select',
admin: {
condition: (_, siblingData) => siblingData?.type === 'global-trigger',
description: 'Global that triggers the workflow',
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.global || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.global = value
return undefined // Virtual field, don't store directly
}
]
},
options: [], // Will be populated dynamically based on available globals
virtual: true,
},
{
name: '__builtin_globalOperation',
}),
triggerField({
name: 'globalOperation',
type: 'select',
admin: {
condition: (_, siblingData) => siblingData?.type === 'global-trigger',
description: 'Global operation that triggers the workflow',
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.globalOperation || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.globalOperation = value
return undefined // Virtual field, don't store directly
}
]
},
options: [
'update'
],
virtual: true,
}
})
]
}

View File

@@ -0,0 +1,50 @@
import type {Field, TextField, SelectField} from "payload"
import type {Trigger} from "./types.js"
type FieldWithName = TextField | SelectField | (Field & { name: string })
type Options = {
slug: string,
fields?: FieldWithName[]
}
export const trigger = ({
slug,
fields
}: Options): Trigger => {
return {
slug,
fields: (fields || []).map(triggerField) as Field[]
}
}
export const triggerField = (field: FieldWithName): Field => ({
...field,
name: '__trigger_' + field.name,
admin: {
...(field.admin as any || {}),
condition: (_, siblingData, __) => {
const previous = field.admin?.condition?.call(null, _, siblingData, __)
return previous !== false // Preserve existing condition if it exists
},
},
hooks: {
afterRead: [
({ siblingData }) => {
const parameters = siblingData?.parameters || {}
return parameters[field.name]
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {
siblingData.parameters = {}
}
siblingData.parameters[field.name] = value
return undefined // Virtual field, don't store directly
}
]
},
virtual: true,
} as Field)

View File

@@ -0,0 +1,6 @@
import type {Field} from "payload"
export type Trigger = {
slug: string
fields: Field[]
}

View File

@@ -1,35 +1,22 @@
import type {Field} from 'payload'
import {triggerField} from "./helpers.js"
export function getWebhookTriggerFields(): Field[] {
return [
{
name: '__builtin_webhookPath',
triggerField({
name: 'webhookPath',
type: 'text',
admin: {
condition: (_, siblingData) => siblingData?.type === 'webhook-trigger',
description: 'URL path for the webhook (e.g., "my-webhook"). Full URL will be /api/workflows-webhook/my-webhook',
},
hooks: {
afterRead: [
({ siblingData }) => {
return siblingData?.parameters?.webhookPath || undefined
}
],
beforeChange: [
({ siblingData, value }) => {
if (!siblingData.parameters) {siblingData.parameters = {}}
siblingData.parameters.webhookPath = value
return undefined // Virtual field, don't store directly
}
]
},
validate: (value: any, {siblingData}: any) => {
if (siblingData?.type === 'webhook-trigger' && !value && !siblingData?.parameters?.webhookPath) {
return 'Webhook path is required for webhook triggers'
}
return true
},
virtual: true,
}
})
]
}