diff --git a/dev/app/(payload)/admin/importMap.js b/dev/app/(payload)/admin/importMap.js index a3bc0e8..9a6e1d7 100644 --- a/dev/app/(payload)/admin/importMap.js +++ b/dev/app/(payload)/admin/importMap.js @@ -1,5 +1,9 @@ - +import { default as default_4845c503d8eeb95a2cf39d519276b9b7 } from '../../../../../components/WorkflowExecutionStatus' +import { default as default_28774f13376b69227276b43eee64e5a1 } from '../../../../../components/StatusCell' +import { default as default_623fcff70b12e3e87839f97bf237499a } from '../../../../../components/ErrorDisplay' export const importMap = { - + "../components/WorkflowExecutionStatus#default": default_4845c503d8eeb95a2cf39d519276b9b7, + "../components/StatusCell#default": default_28774f13376b69227276b43eee64e5a1, + "../components/ErrorDisplay#default": default_623fcff70b12e3e87839f97bf237499a } diff --git a/dev/payload-types.ts b/dev/payload-types.ts index 3aa227d..ab60d39 100644 --- a/dev/payload-types.ts +++ b/dev/payload-types.ts @@ -92,7 +92,7 @@ export interface Config { 'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect; }; db: { - defaultIDType: number; + defaultIDType: string; }; globals: {}; globalsSelect: {}; @@ -136,7 +136,7 @@ export interface UserAuthOperations { * via the `definition` "posts". */ export interface Post { - id: number; + id: string; content?: string | null; updatedAt: string; createdAt: string; @@ -146,7 +146,7 @@ export interface Post { * via the `definition` "media". */ export interface Media { - id: number; + id: string; updatedAt: string; createdAt: string; url?: string | null; @@ -164,9 +164,9 @@ export interface Media { * via the `definition` "auditLog". */ export interface AuditLog { - id: number; - post?: (number | null) | Post; - user?: (number | null) | User; + id: string; + post?: (string | null) | Post; + user?: (string | null) | User; message?: string | null; updatedAt: string; createdAt: string; @@ -176,7 +176,7 @@ export interface AuditLog { * via the `definition` "users". */ export interface User { - id: number; + id: string; updatedAt: string; createdAt: string; email: string; @@ -202,7 +202,7 @@ export interface User { * via the `definition` "workflows". */ export interface Workflow { - id: number; + id: string; /** * Human-readable name for the workflow */ @@ -214,36 +214,45 @@ export interface Workflow { triggers?: | { type?: ('collection-trigger' | 'webhook-trigger' | 'global-trigger' | 'cron-trigger') | null; + parameters?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; /** * Collection that triggers the workflow */ - collectionSlug?: ('posts' | 'media') | null; + __builtin_collectionSlug?: ('posts' | 'media') | null; /** * Collection operation that triggers the workflow */ - operation?: ('create' | 'delete' | 'read' | 'update') | null; + __builtin_operation?: ('create' | 'delete' | 'read' | 'update') | null; /** - * URL path for the webhook (e.g., "my-webhook"). Full URL will be /api/workflows/webhook/my-webhook + * URL path for the webhook (e.g., "my-webhook"). Full URL will be /api/workflows-webhook/my-webhook */ - webhookPath?: string | null; + __builtin_webhookPath?: string | null; /** * Global that triggers the workflow */ - global?: string | null; + __builtin_global?: string | null; /** * Global operation that triggers the workflow */ - globalOperation?: 'update' | null; + __builtin_globalOperation?: 'update' | null; /** * Cron expression for scheduled execution (e.g., "0 0 * * *" for daily at midnight) */ - cronExpression?: string | null; + __builtin_cronExpression?: string | null; /** * Timezone for cron execution (e.g., "America/New_York", "Europe/London"). Defaults to UTC. */ - timezone?: string | null; + __builtin_timezone?: string | null; /** - * JSONPath expression that must evaluate to true for this trigger to execute the workflow (e.g., "$.doc.status == 'published'") + * JSONPath expression that must evaluate to true for this trigger to execute the workflow (e.g., "$.trigger.doc.status == 'published'") */ condition?: string | null; id?: string | null; @@ -253,7 +262,18 @@ export interface Workflow { | { step?: ('http-request-step' | 'create-document') | null; name?: string | null; - input?: + /** + * The URL to make the HTTP request to + */ + url?: string | null; + /** + * HTTP method to use + */ + method?: ('GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH') | null; + /** + * HTTP headers as JSON object (e.g., {"Content-Type": "application/json"}) + */ + headers?: | { [k: string]: unknown; } @@ -262,6 +282,80 @@ export interface Workflow { | number | boolean | null; + /** + * Request body data. Use JSONPath to reference values (e.g., {"postId": "$.trigger.doc.id", "title": "$.trigger.doc.title"}) + */ + body?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Request timeout in milliseconds (default: 30000) + */ + timeout?: number | null; + authentication?: { + /** + * Authentication method + */ + type?: ('none' | 'bearer' | 'basic' | 'apikey') | null; + /** + * Bearer token value + */ + token?: string | null; + /** + * Basic auth username + */ + username?: string | null; + /** + * Basic auth password + */ + password?: string | null; + /** + * API key header name (e.g., "X-API-Key") + */ + headerName?: string | null; + /** + * API key value + */ + headerValue?: string | null; + }; + /** + * Number of retry attempts on failure (max: 5) + */ + retries?: number | null; + /** + * Delay between retries in milliseconds + */ + retryDelay?: number | null; + /** + * The collection slug to create a document in + */ + collectionSlug?: string | null; + /** + * The document data to create. Use JSONPath to reference trigger data (e.g., {"title": "$.trigger.doc.title", "author": "$.trigger.doc.author"}) + */ + data?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Create as draft (if collection has drafts enabled) + */ + draft?: boolean | null; + /** + * Locale for the document (if localization is enabled) + */ + locale?: string | null; /** * Step names that must complete before this step can run */ @@ -282,11 +376,11 @@ export interface Workflow { * via the `definition` "workflow-runs". */ export interface WorkflowRun { - id: number; + id: string; /** * Reference to the workflow that was executed */ - workflow: number | Workflow; + workflow: string | Workflow; /** * Version of the workflow that was executed */ @@ -380,7 +474,7 @@ export interface WorkflowRun { * via the `definition` "payload-jobs". */ export interface PayloadJob { - id: number; + id: string; /** * Input data provided to the job */ @@ -472,40 +566,40 @@ export interface PayloadJob { * via the `definition` "payload-locked-documents". */ export interface PayloadLockedDocument { - id: number; + id: string; document?: | ({ relationTo: 'posts'; - value: number | Post; + value: string | Post; } | null) | ({ relationTo: 'media'; - value: number | Media; + value: string | Media; } | null) | ({ relationTo: 'auditLog'; - value: number | AuditLog; + value: string | AuditLog; } | null) | ({ relationTo: 'workflows'; - value: number | Workflow; + value: string | Workflow; } | null) | ({ relationTo: 'workflow-runs'; - value: number | WorkflowRun; + value: string | WorkflowRun; } | null) | ({ relationTo: 'users'; - value: number | User; + value: string | User; } | null) | ({ relationTo: 'payload-jobs'; - value: number | PayloadJob; + value: string | PayloadJob; } | null); globalSlug?: string | null; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; updatedAt: string; createdAt: string; @@ -515,10 +609,10 @@ export interface PayloadLockedDocument { * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: number; + id: string; user: { relationTo: 'users'; - value: number | User; + value: string | User; }; key?: string | null; value?: @@ -538,7 +632,7 @@ export interface PayloadPreference { * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: number; + id: string; name?: string | null; batch?: number | null; updatedAt: string; @@ -592,13 +686,14 @@ export interface WorkflowsSelect { | T | { type?: T; - collectionSlug?: T; - operation?: T; - webhookPath?: T; - global?: T; - globalOperation?: T; - cronExpression?: T; - timezone?: T; + parameters?: T; + __builtin_collectionSlug?: T; + __builtin_operation?: T; + __builtin_webhookPath?: T; + __builtin_global?: T; + __builtin_globalOperation?: T; + __builtin_cronExpression?: T; + __builtin_timezone?: T; condition?: T; id?: T; }; @@ -607,7 +702,27 @@ export interface WorkflowsSelect { | { step?: T; name?: T; - input?: T; + url?: T; + method?: T; + headers?: T; + body?: T; + timeout?: T; + authentication?: + | T + | { + type?: T; + token?: T; + username?: T; + password?: T; + headerName?: T; + headerValue?: T; + }; + retries?: T; + retryDelay?: T; + collectionSlug?: T; + data?: T; + draft?: T; + locale?: T; dependencies?: T; condition?: T; id?: T; @@ -736,10 +851,118 @@ export interface TaskWorkflowCronExecutor { */ export interface TaskHttpRequestStep { input: { - url?: string | null; + /** + * The URL to make the HTTP request to + */ + url: string; + /** + * HTTP method to use + */ + method?: ('GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH') | null; + /** + * HTTP headers as JSON object (e.g., {"Content-Type": "application/json"}) + */ + headers?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Request body data. Use JSONPath to reference values (e.g., {"postId": "$.trigger.doc.id", "title": "$.trigger.doc.title"}) + */ + body?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Request timeout in milliseconds (default: 30000) + */ + timeout?: number | null; + authentication?: { + /** + * Authentication method + */ + type?: ('none' | 'bearer' | 'basic' | 'apikey') | null; + /** + * Bearer token value + */ + token?: string | null; + /** + * Basic auth username + */ + username?: string | null; + /** + * Basic auth password + */ + password?: string | null; + /** + * API key header name (e.g., "X-API-Key") + */ + headerName?: string | null; + /** + * API key value + */ + headerValue?: string | null; + }; + /** + * Number of retry attempts on failure (max: 5) + */ + retries?: number | null; + /** + * Delay between retries in milliseconds + */ + retryDelay?: number | null; }; output: { - response?: string | null; + /** + * HTTP status code + */ + status?: number | null; + /** + * HTTP status text + */ + statusText?: string | null; + /** + * Response headers + */ + headers?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Response body + */ + body?: string | null; + /** + * Parsed response data (if JSON) + */ + data?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; + /** + * Request duration in milliseconds + */ + duration?: number | null; }; } /** @@ -753,7 +976,7 @@ export interface TaskCreateDocument { */ collectionSlug: string; /** - * The document data to create + * The document data to create. Use JSONPath to reference trigger data (e.g., {"title": "$.trigger.doc.title", "author": "$.trigger.doc.author"}) */ data: | { diff --git a/src/collections/Workflow.ts b/src/collections/Workflow.ts index 002b592..24cf290 100644 --- a/src/collections/Workflow.ts +++ b/src/collections/Workflow.ts @@ -41,7 +41,7 @@ export const createWorkflowCollection: (options: WorkflowsPlug type: 'ui', admin: { components: { - Field: '@/components/WorkflowExecutionStatus' + Field: '../components/WorkflowExecutionStatus' }, condition: (data) => !!data?.id // Only show for existing workflows } diff --git a/src/collections/WorkflowRuns.ts b/src/collections/WorkflowRuns.ts index a1b6e59..3a63a62 100644 --- a/src/collections/WorkflowRuns.ts +++ b/src/collections/WorkflowRuns.ts @@ -40,7 +40,7 @@ export const WorkflowRunsCollection: CollectionConfig = { admin: { description: 'Current execution status', components: { - Cell: '@/components/StatusCell' + Cell: '../components/StatusCell' } }, defaultValue: 'pending', @@ -141,7 +141,7 @@ export const WorkflowRunsCollection: CollectionConfig = { description: 'Error message if workflow execution failed', condition: (_, siblingData) => siblingData?.status === 'failed', components: { - Field: '@/components/ErrorDisplay' + Field: '../components/ErrorDisplay' } }, }, diff --git a/src/components/ErrorDisplay.tsx.disabled b/src/components/ErrorDisplay.tsx similarity index 96% rename from src/components/ErrorDisplay.tsx.disabled rename to src/components/ErrorDisplay.tsx index 1fc3824..62b54a3 100644 --- a/src/components/ErrorDisplay.tsx.disabled +++ b/src/components/ErrorDisplay.tsx @@ -166,14 +166,15 @@ export const ErrorDisplay: React.FC = ({ {/* Technical Details Toggle */}
- +
+ +
{expanded && (
+ value?: unknown +} + +interface ValidationContext { + siblingData: Record +} + /** * Creates a virtual field for a trigger parameter that stores its value in the parameters JSON field * @@ -25,7 +41,7 @@ import type { CustomTriggerConfig } from '../plugin/config-types.js' * } * ``` */ -export function createTriggerField(field: any, triggerSlug: string): Field { +export function createTriggerField(field: FieldWithName, triggerSlug: string): Field { const originalName = field.name if (!originalName) { throw new Error('Field must have a name property') @@ -34,61 +50,70 @@ export function createTriggerField(field: any, triggerSlug: string): Field { // Create a unique field name by prefixing with trigger slug const uniqueFieldName = `__trigger_${triggerSlug}_${originalName}` - const resultField: any = { + const resultField: Record = { ...field, - name: uniqueFieldName, - virtual: true, admin: { - ...(field.admin || {}), - condition: (data: any, siblingData: any) => { + ...(field.admin as Record || {}), + condition: (data: unknown, siblingData: Record) => { // Only show this field when the trigger type matches const triggerMatches = siblingData?.type === triggerSlug // If the original field had a condition, combine it with our trigger condition - if (field.admin?.condition) { - return triggerMatches && field.admin.condition(data, siblingData) + const originalCondition = (field.admin as Record)?.condition + if (originalCondition && typeof originalCondition === 'function') { + return triggerMatches && (originalCondition as (data: unknown, siblingData: Record) => boolean)(data, siblingData) } return triggerMatches } }, hooks: { - ...(field.hooks || {}), + ...(field.hooks as Record || {}), afterRead: [ - ...(field.hooks?.afterRead || []), - ({ siblingData }: any) => { + ...((field.hooks as Record)?.afterRead || []), + ({ siblingData }: HookContext) => { // Read the value from the parameters JSON field - return siblingData?.parameters?.[originalName] ?? field.defaultValue + const parameters = siblingData?.parameters as Record + return parameters?.[originalName] ?? (field as Record).defaultValue } ], beforeChange: [ - ...(field.hooks?.beforeChange || []), - ({ value, siblingData }: any) => { + ...((field.hooks as Record)?.beforeChange || []), + ({ siblingData, value }: HookContext) => { // Store the value in the parameters JSON field if (!siblingData.parameters) { siblingData.parameters = {} } - siblingData.parameters[originalName] = value + const parameters = siblingData.parameters as Record + parameters[originalName] = value return undefined // Virtual field, don't store directly } ] - } + }, + name: uniqueFieldName, + virtual: true, } // Only add validate if the field supports it (data fields) - if (field.validate || field.required) { - resultField.validate = (value: any, args: any) => { - const paramValue = value ?? args.siblingData?.parameters?.[originalName] + const hasValidation = (field as Record).validate || (field as Record).required + if (hasValidation) { + resultField.validate = (value: unknown, args: ValidationContext) => { + const parameters = args.siblingData?.parameters as Record + const paramValue = value ?? parameters?.[originalName] // Check required validation - if (field.required && args.siblingData?.type === triggerSlug && !paramValue) { - const label = field.label || field.admin?.description || originalName + const isRequired = (field as Record).required + if (isRequired && args.siblingData?.type === triggerSlug && !paramValue) { + const fieldLabel = (field as Record).label as string + const adminDesc = ((field as Record).admin as Record)?.description as string + const label = fieldLabel || adminDesc || originalName return `${label} is required for ${triggerSlug}` } // Run original validation if present - if (field.validate) { - return field.validate(paramValue, args) + const originalValidate = (field as Record).validate + if (originalValidate && typeof originalValidate === 'function') { + return (originalValidate as (value: unknown, args: ValidationContext) => boolean | string)(paramValue, args) } return true @@ -125,7 +150,7 @@ export function createTriggerField(field: any, triggerSlug: string): Field { * ]) * ``` */ -export function createTrigger(slug: string, fields: Field[]): CustomTriggerConfig { +export function createTrigger(slug: string, fields: FieldWithName[]): CustomTriggerConfig { return { slug, inputs: fields.map(field => createTriggerField(field, slug))