mirror of
https://github.com/xtr-dev/payload-automation.git
synced 2025-12-10 17:03:22 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd85f90ef1 | |||
| 38fbb1922a | |||
| dfcc5c0fce | |||
| 089e12ac7a | |||
| 8ff65ca7c3 | |||
| bdfc311009 | |||
| 3c54f00f57 | |||
| cbb74206e9 | |||
| 41c4d8bdcb | |||
| 46c9f11534 | |||
| 08a4022a41 | |||
| c24610b3d9 | |||
| 87893ac612 | |||
| a711fbdbea | |||
| 4adc5cbdaa |
38
CHANGELOG.md
Normal file
38
CHANGELOG.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to the PayloadCMS Automation Plugin will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.0.16] - 2025-09-01
|
||||
|
||||
### Fixed
|
||||
- **Critical Bug**: Removed problematic `hooksInitialized` flag that prevented proper hook registration in development environments
|
||||
- **Silent Failures**: Added comprehensive error logging with "AUTOMATION PLUGIN:" prefix for easier debugging
|
||||
- **Hook Execution**: Added try/catch blocks in hook execution to prevent silent failures and ensure workflow execution continues
|
||||
- **Development Mode**: Fixed issue where workflows would not execute even when properly configured due to hook registration being skipped
|
||||
|
||||
### Changed
|
||||
- Enhanced logging throughout the hook execution pipeline for better debugging visibility
|
||||
- Improved error handling to prevent workflow execution failures from breaking other hooks
|
||||
|
||||
### Migration Notes
|
||||
- No breaking changes - this is a critical bug fix release
|
||||
- Existing workflows should now execute properly after updating to this version
|
||||
- Enhanced logging will provide better visibility into workflow execution
|
||||
|
||||
## [0.0.15] - 2025-08-XX
|
||||
|
||||
### Changed
|
||||
- Updated workflow condition evaluation to use JSONPath expressions
|
||||
- Changed step configuration from `type`/`inputs` to `step`/`input`
|
||||
- Updated workflow collection schema for improved flexibility
|
||||
|
||||
## [0.0.14] - 2025-08-XX
|
||||
|
||||
### Added
|
||||
- Initial workflow automation functionality
|
||||
- Collection trigger support
|
||||
- Step execution engine
|
||||
- Basic workflow management
|
||||
116
CRITICAL-CONFIG-FIX.md
Normal file
116
CRITICAL-CONFIG-FIX.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# 🚨 CRITICAL: Configuration Field Name Issue Found
|
||||
|
||||
## ❌ The Root Problem
|
||||
|
||||
Your plugin configuration is using the **wrong field name**!
|
||||
|
||||
### What You're Probably Using (WRONG):
|
||||
```javascript
|
||||
automationPlugin({
|
||||
collections: ['orders', 'users', 'products'], // ❌ Wrong field name
|
||||
steps: [...],
|
||||
})
|
||||
```
|
||||
|
||||
### What You MUST Use (CORRECT):
|
||||
```javascript
|
||||
automationPlugin({
|
||||
collectionTriggers: { // ✅ Correct field name
|
||||
'orders': true,
|
||||
'users': true,
|
||||
'products': true
|
||||
},
|
||||
steps: [...],
|
||||
})
|
||||
```
|
||||
|
||||
## 🔧 Immediate Fix Required
|
||||
|
||||
**Update your `payload.config.ts` file:**
|
||||
|
||||
```typescript
|
||||
import { automationPlugin } from '@xtr-dev/payload-automation'
|
||||
|
||||
export default buildConfig({
|
||||
// ... your other config
|
||||
|
||||
plugins: [
|
||||
automationPlugin({
|
||||
collectionTriggers: { // ← CHANGE THIS FIELD NAME
|
||||
orders: true, // Enable all hooks (create, read, update, delete)
|
||||
users: true,
|
||||
products: true
|
||||
},
|
||||
steps: [
|
||||
// ... your step configurations
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 🎯 Why This Fixes Everything
|
||||
|
||||
1. **Hook Registration**: The plugin only registers hooks for collections listed in `collectionTriggers`
|
||||
2. **No Hooks = No Execution**: If `collectionTriggers` is empty/missing, no hooks get registered
|
||||
3. **Silent Failure**: The plugin logs "No collection triggers configured" and returns early
|
||||
|
||||
## 🔍 Advanced Configuration Options
|
||||
|
||||
You can also be more specific about which operations trigger workflows:
|
||||
|
||||
```javascript
|
||||
automationPlugin({
|
||||
collectionTriggers: {
|
||||
orders: {
|
||||
update: true, // Only trigger on updates
|
||||
create: true // Only trigger on creates
|
||||
// read and delete are false by default
|
||||
},
|
||||
users: true // Enable all operations
|
||||
},
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## ✅ Expected Results After Fix
|
||||
|
||||
Once you update your configuration and restart:
|
||||
|
||||
1. **Plugin logs will show**:
|
||||
```
|
||||
Starting collection hook registration
|
||||
Collection hooks registered successfully - collectionSlug: "orders"
|
||||
```
|
||||
|
||||
2. **Hook counts will be > 0**:
|
||||
```javascript
|
||||
payload.collections.orders.config.hooks.afterChange.length
|
||||
// Should return a number > 0
|
||||
```
|
||||
|
||||
3. **Workflow execution will work**:
|
||||
- "AUTOMATION PLUGIN: Collection hook triggered" messages
|
||||
- Workflow runs created in database
|
||||
- Jobs processing successfully
|
||||
|
||||
## 🆘 If Still Not Working
|
||||
|
||||
If you fix the configuration and it still doesn't work:
|
||||
|
||||
1. **Check your exact collection slugs**:
|
||||
```javascript
|
||||
console.log('Available collections:', Object.keys(payload.collections))
|
||||
```
|
||||
|
||||
2. **Verify case sensitivity**: Collection slugs are case-sensitive
|
||||
- Use exactly what appears in `Object.keys(payload.collections)`
|
||||
|
||||
3. **Restart completely**:
|
||||
- Stop dev server
|
||||
- Clear any caches
|
||||
- Restart with new configuration
|
||||
|
||||
---
|
||||
|
||||
**This configuration field name issue explains why no hooks were being registered, despite the plugin loading successfully. The v0.0.16 bug fix was valid, but this configuration issue was preventing hooks from being registered in the first place.**
|
||||
151
CUSTOMER-DIAGNOSTIC.md
Normal file
151
CUSTOMER-DIAGNOSTIC.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 🔍 CRITICAL DIAGNOSTIC: Why The Plugin Works Locally But Not For You
|
||||
|
||||
## The Key Insight
|
||||
|
||||
Our tests work because we define collections **inline** in the config:
|
||||
|
||||
```typescript
|
||||
// OUR TEST ENVIRONMENT - WORKS
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: 'posts',
|
||||
fields: [...],
|
||||
// Collection defined RIGHT HERE
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
workflowsPlugin({...}) // Plugin can see collections above
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## The Likely Customer Setup
|
||||
|
||||
You probably have collections defined **separately**:
|
||||
|
||||
```typescript
|
||||
// YOUR ENVIRONMENT - LIKELY STRUCTURE
|
||||
import { Orders } from './collections/Orders'
|
||||
import { Users } from './collections/Users'
|
||||
import { Products } from './collections/Products'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
Orders, // Imported from separate file
|
||||
Users, // Imported from separate file
|
||||
Products // Imported from separate file
|
||||
],
|
||||
plugins: [
|
||||
workflowsPlugin({...}) // Plugin runs but collections might be different
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## The Critical Question
|
||||
|
||||
**How are your collections defined?**
|
||||
|
||||
### Option 1: Separate Files (Most Common)
|
||||
```typescript
|
||||
// collections/Orders.ts
|
||||
export const Orders: CollectionConfig = {
|
||||
slug: 'orders',
|
||||
hooks: {
|
||||
// Your existing hooks here
|
||||
},
|
||||
fields: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Factory Functions
|
||||
```typescript
|
||||
// collections/Orders.ts
|
||||
export const Orders = (): CollectionConfig => ({
|
||||
slug: 'orders',
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### Option 3: Class-based or Complex Setup
|
||||
```typescript
|
||||
// Something more complex that might not be in config.collections yet
|
||||
```
|
||||
|
||||
## 🚨 THE DIAGNOSTIC TEST
|
||||
|
||||
Add this to your payload.config.ts BEFORE the workflowsPlugin:
|
||||
|
||||
```typescript
|
||||
export default buildConfig({
|
||||
collections: [Orders, Users, Products],
|
||||
plugins: [
|
||||
// ADD THIS DIAGNOSTIC PLUGIN FIRST
|
||||
(config) => {
|
||||
console.log('🔍 DIAGNOSTIC: Collections in config:')
|
||||
console.log(' - config.collections exists?', !!config.collections)
|
||||
console.log(' - config.collections length:', config.collections?.length)
|
||||
console.log(' - Collection slugs:', config.collections?.map(c => c.slug))
|
||||
|
||||
// Check if orders collection has hooks already
|
||||
const ordersConfig = config.collections?.find(c => c.slug === 'orders')
|
||||
console.log(' - Orders collection found?', !!ordersConfig)
|
||||
console.log(' - Orders has hooks?', !!ordersConfig?.hooks)
|
||||
console.log(' - Orders afterChange hooks:', ordersConfig?.hooks?.afterChange?.length || 0)
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
// THEN your automation plugin
|
||||
workflowsPlugin({...})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## 🎯 What This Will Tell Us
|
||||
|
||||
1. **If collections show up**: The plugin should work with v0.0.20
|
||||
2. **If collections are empty/undefined**: That's why hooks aren't registering
|
||||
3. **If orders already has hooks**: There might be a conflict
|
||||
|
||||
## 💡 The Likely Solution
|
||||
|
||||
If your collections are in separate files, you might need to:
|
||||
|
||||
### Option A: Add hooks directly to collection files
|
||||
```typescript
|
||||
// collections/Orders.ts
|
||||
import { automationHook } from '@xtr-dev/payload-automation/hooks' // We'd need to export this
|
||||
|
||||
export const Orders: CollectionConfig = {
|
||||
slug: 'orders',
|
||||
hooks: {
|
||||
afterChange: [
|
||||
automationHook, // Add directly here
|
||||
// ... your other hooks
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option B: Modify collections before passing to buildConfig
|
||||
```typescript
|
||||
// payload.config.ts
|
||||
import { Orders } from './collections/Orders'
|
||||
import { addAutomationHooks } from '@xtr-dev/payload-automation/utils' // We'd need to create this
|
||||
|
||||
const OrdersWithAutomation = addAutomationHooks(Orders)
|
||||
|
||||
export default buildConfig({
|
||||
collections: [OrdersWithAutomation, Users, Products],
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## 🔑 The Bottom Line
|
||||
|
||||
**The plugin works when collections are defined inline because they exist in `config.collections` when the plugin runs.**
|
||||
|
||||
**If your collections are imported from separate files, they might not be in the right structure for the plugin to modify them.**
|
||||
|
||||
Run the diagnostic above and share the console output - it will tell us exactly why the hooks aren't registering in your environment!
|
||||
145
UPDATE-GUIDANCE-v0.0.16.md
Normal file
145
UPDATE-GUIDANCE-v0.0.16.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 🚨 Critical Update: PayloadCMS Automation Plugin v0.0.16
|
||||
|
||||
## ⚡ Immediate Action Required
|
||||
|
||||
A **critical bug** has been identified and fixed in v0.0.15 that prevented workflows from executing in development environments. **Please update immediately** to resolve workflow execution issues.
|
||||
|
||||
## 🔧 Quick Update Steps
|
||||
|
||||
### 1. Update the Package
|
||||
```bash
|
||||
npm update @xtr-dev/payload-automation
|
||||
# OR
|
||||
yarn upgrade @xtr-dev/payload-automation
|
||||
# OR
|
||||
pnpm update @xtr-dev/payload-automation
|
||||
```
|
||||
|
||||
### 2. Verify Version
|
||||
Check that you're now on v0.0.16:
|
||||
```bash
|
||||
npm list @xtr-dev/payload-automation
|
||||
```
|
||||
|
||||
### 3. Restart Your Development Server
|
||||
```bash
|
||||
# Stop your current dev server (Ctrl+C)
|
||||
# Then restart
|
||||
npm run dev
|
||||
# OR
|
||||
yarn dev
|
||||
# OR
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### 4. Test Your Workflows
|
||||
Your workflows should now execute properly! Look for these log messages:
|
||||
|
||||
```
|
||||
[payload-automation] Plugin initialized successfully - all hooks registered
|
||||
AUTOMATION PLUGIN: Collection hook triggered
|
||||
executeTriggeredWorkflows called
|
||||
Found workflows to check
|
||||
```
|
||||
|
||||
## 🐛 What Was Fixed
|
||||
|
||||
### Critical Bug: Hook Registration Failure
|
||||
- **Problem**: The `hooksInitialized` flag prevented proper hook registration in development mode
|
||||
- **Symptom**: Workflows would not execute even when correctly configured
|
||||
- **Fix**: Removed the problematic flag, ensuring hooks register on every initialization
|
||||
|
||||
### Enhanced Debugging
|
||||
- **Added**: Comprehensive logging with "AUTOMATION PLUGIN:" prefix
|
||||
- **Added**: Try/catch blocks to prevent silent failures
|
||||
- **Added**: Better error messages and stack traces
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### If workflows still don't execute after updating:
|
||||
|
||||
1. **Check your workflow configuration** (should now use v0.0.15+ schema):
|
||||
```javascript
|
||||
// ✅ Correct v0.0.15+ schema
|
||||
{
|
||||
triggers: [{
|
||||
type: 'collection-trigger',
|
||||
collectionSlug: 'orders',
|
||||
operation: 'update',
|
||||
condition: '$.trigger.doc.status == "Paid"' // JSONPath format
|
||||
}],
|
||||
steps: [{
|
||||
step: 'uppercaseText', // 'step' not 'type'
|
||||
name: 'Process Order',
|
||||
input: { // 'input' not 'inputs'
|
||||
inputText: '$.trigger.doc.orderName has been paid!'
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
2. **Verify plugin configuration** includes your collections:
|
||||
```javascript
|
||||
automationPlugin({
|
||||
collections: ['orders', 'users', 'products'], // Must include target collections
|
||||
// ... other config
|
||||
})
|
||||
```
|
||||
|
||||
3. **Check the logs** for "AUTOMATION PLUGIN:" messages during hook execution
|
||||
|
||||
4. **Ensure workflow status**: If using versioning, make sure workflows are "Published" not "Draft"
|
||||
|
||||
## 📋 Schema Changes Recap (from v0.0.14 → v0.0.15+)
|
||||
|
||||
If you haven't updated your workflows since v0.0.14, you'll also need to update the schema:
|
||||
|
||||
### Triggers
|
||||
```javascript
|
||||
// ❌ OLD v0.0.14
|
||||
conditions: [
|
||||
{ field: 'status', operator: 'equals', value: 'Paid' }
|
||||
]
|
||||
|
||||
// ✅ NEW v0.0.15+
|
||||
condition: '$.trigger.doc.status == "Paid"'
|
||||
```
|
||||
|
||||
### Steps
|
||||
```javascript
|
||||
// ❌ OLD v0.0.14
|
||||
{
|
||||
type: 'uppercaseText',
|
||||
inputs: { inputText: 'Hello' }
|
||||
}
|
||||
|
||||
// ✅ NEW v0.0.15+
|
||||
{
|
||||
step: 'uppercaseText',
|
||||
input: { inputText: 'Hello' }
|
||||
}
|
||||
```
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
If you're still experiencing issues after updating to v0.0.16:
|
||||
|
||||
1. **Check console logs** for "AUTOMATION PLUGIN:" messages
|
||||
2. **Verify your workflow schema** matches v0.0.15+ format
|
||||
3. **Confirm plugin configuration** includes target collections
|
||||
4. **File an issue** at https://github.com/anthropics/claude-code/issues with:
|
||||
- PayloadCMS version
|
||||
- Complete console logs during workflow execution
|
||||
- Your workflow configuration (sanitized)
|
||||
|
||||
## ✅ Success Indicators
|
||||
|
||||
After updating, you should see:
|
||||
- ✅ Workflow runs created in `workflow-runs` collection
|
||||
- ✅ "AUTOMATION PLUGIN:" log messages during hook execution
|
||||
- ✅ Jobs appearing in `payload-jobs` collection
|
||||
- ✅ Workflow steps executing successfully
|
||||
|
||||
---
|
||||
|
||||
**This is a critical bug fix release - no breaking changes, just fixes the core functionality that wasn't working in v0.0.15.**
|
||||
67
debug-customer-setup.js
Normal file
67
debug-customer-setup.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// Debug script to identify customer-side configuration issues
|
||||
// Run this in your environment to pinpoint the problem
|
||||
|
||||
console.log('🔍 === CUSTOMER ENVIRONMENT DEBUGGING ===')
|
||||
|
||||
// This script needs to be run in your actual environment
|
||||
// Copy this logic into your own debugging script
|
||||
|
||||
const debugChecklist = {
|
||||
"Plugin Version": "Check package.json for @xtr-dev/payload-automation version",
|
||||
"Plugin Configuration": "Verify automationPlugin() is called with correct collections array",
|
||||
"Database Collections": "Confirm 'workflows' and 'workflow-runs' collections exist",
|
||||
"Hook Registration": "Check if afterChange hooks are actually registered on orders collection",
|
||||
"Workflow Status": "Verify workflow document has _status: 'published'",
|
||||
"Workflow Structure": "Confirm triggers array and steps array are populated",
|
||||
"Order Collection": "Verify orders collection exists and is configured in plugin",
|
||||
"PayloadCMS Version": "Check if you're using compatible Payload version",
|
||||
"Environment": "Development vs Production database differences"
|
||||
}
|
||||
|
||||
console.log('\n📋 Debugging Checklist for Your Environment:')
|
||||
Object.entries(debugChecklist).forEach(([check, description], i) => {
|
||||
console.log(`${i + 1}. ${check}: ${description}`)
|
||||
})
|
||||
|
||||
console.log('\n🔍 Specific Things to Check in YOUR Environment:')
|
||||
|
||||
console.log('\n1. Plugin Configuration (payload.config.ts):')
|
||||
console.log(` automationPlugin({
|
||||
collections: ['orders', 'users', 'products'], // <- Must include 'orders'
|
||||
// ... other config
|
||||
})`)
|
||||
|
||||
console.log('\n2. Database Query (run this in your environment):')
|
||||
console.log(` const workflows = await payload.find({
|
||||
collection: 'workflows',
|
||||
depth: 2
|
||||
})
|
||||
console.log('Found workflows:', workflows.docs.length)
|
||||
console.log('Workflow details:', JSON.stringify(workflows.docs, null, 2))`)
|
||||
|
||||
console.log('\n3. Hook Registration Check:')
|
||||
console.log(` const orderCollection = payload.collections.orders
|
||||
console.log('afterChange hooks:', orderCollection.config.hooks?.afterChange?.length)`)
|
||||
|
||||
console.log('\n4. Manual Hook Trigger Test:')
|
||||
console.log(` // Manually call the executor method
|
||||
const executor = // get executor instance somehow
|
||||
await executor.executeTriggeredWorkflows('orders', 'update', updatedDoc, previousDoc, req)`)
|
||||
|
||||
console.log('\n5. Most Likely Issues:')
|
||||
console.log(' - Plugin not configured with "orders" in collections array')
|
||||
console.log(' - Workflow is in draft status (not published)')
|
||||
console.log(' - Database connection issue (different DB in dev vs prod)')
|
||||
console.log(' - PayloadCMS version compatibility issue')
|
||||
console.log(' - Hook execution order (automation hook not running last)')
|
||||
|
||||
console.log('\n💡 Quick Test - Add this to your order update code:')
|
||||
console.log(` console.log('🔍 DEBUG: About to update order')
|
||||
const result = await payload.update({ ... })
|
||||
console.log('🔍 DEBUG: Order updated, hooks should have fired')
|
||||
|
||||
// Check immediately after
|
||||
const runs = await payload.find({ collection: 'workflow-runs' })
|
||||
console.log('🔍 DEBUG: Workflow runs after update:', runs.docs.length)`)
|
||||
|
||||
process.exit(0)
|
||||
210
debug-workflow-execution.js
Normal file
210
debug-workflow-execution.js
Normal file
@@ -0,0 +1,210 @@
|
||||
// Enhanced debugging script for workflow execution issues
|
||||
const { getPayload } = require('payload')
|
||||
const { JSONPath } = require('jsonpath-plus')
|
||||
|
||||
async function debugWorkflowExecution() {
|
||||
const payload = await getPayload({
|
||||
config: require('./dev/payload.config.ts').default
|
||||
})
|
||||
|
||||
console.log('🔍 === WORKFLOW EXECUTION DEBUGGING ===')
|
||||
|
||||
// Step 1: Verify workflow exists and has correct structure
|
||||
console.log('\n📋 Step 1: Finding workflows...')
|
||||
const workflows = await payload.find({
|
||||
collection: 'workflows',
|
||||
depth: 2,
|
||||
limit: 100
|
||||
})
|
||||
|
||||
console.log(`Found ${workflows.docs.length} workflows:`)
|
||||
|
||||
for (const workflow of workflows.docs) {
|
||||
console.log(`\n Workflow: "${workflow.name}" (ID: ${workflow.id})`)
|
||||
console.log(` Enabled: ${workflow.enabled !== false}`)
|
||||
console.log(` Triggers: ${JSON.stringify(workflow.triggers, null, 4)}`)
|
||||
console.log(` Steps: ${JSON.stringify(workflow.steps, null, 4)}`)
|
||||
}
|
||||
|
||||
// Step 2: Create test order and simulate the trigger context
|
||||
console.log('\n📦 Step 2: Creating test order...')
|
||||
|
||||
const testOrder = await payload.create({
|
||||
collection: 'orders',
|
||||
data: {
|
||||
orderName: 'Debug Test Order - ' + Date.now(),
|
||||
status: 'Unpaid',
|
||||
customerEmail: 'debug@example.com',
|
||||
totalPrice: 1500,
|
||||
items: [
|
||||
{
|
||||
name: 'Debug Item',
|
||||
quantity: 1,
|
||||
price: 1500
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Created test order: ${testOrder.id} with status: "${testOrder.status}"`)
|
||||
|
||||
// Step 3: Test JSONPath condition evaluation directly
|
||||
console.log('\n🧪 Step 3: Testing JSONPath condition evaluation...')
|
||||
|
||||
// Simulate the execution context that would be created during hook execution
|
||||
const simulatedContext = {
|
||||
steps: {},
|
||||
trigger: {
|
||||
type: 'collection',
|
||||
collection: 'orders',
|
||||
doc: { ...testOrder, status: 'Paid' }, // Simulating the updated status
|
||||
operation: 'update',
|
||||
previousDoc: testOrder, // Original order with 'Unpaid' status
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Simulated context:')
|
||||
console.log(' - Trigger type:', simulatedContext.trigger.type)
|
||||
console.log(' - Collection:', simulatedContext.trigger.collection)
|
||||
console.log(' - Doc status:', simulatedContext.trigger.doc.status)
|
||||
console.log(' - Previous doc status:', simulatedContext.trigger.previousDoc.status)
|
||||
|
||||
// Test the condition used in workflow
|
||||
const condition = '$.doc.status == "Paid"'
|
||||
console.log(`\nTesting condition: ${condition}`)
|
||||
|
||||
try {
|
||||
// Test left side JSONPath resolution
|
||||
const leftResult = JSONPath({
|
||||
json: simulatedContext,
|
||||
path: '$.trigger.doc.status',
|
||||
wrap: false
|
||||
})
|
||||
console.log(` - Left side ($.trigger.doc.status): ${JSON.stringify(leftResult)} (type: ${typeof leftResult})`)
|
||||
|
||||
// Test the comparison manually
|
||||
const comparisonMatch = condition.match(/^(.+?)\s*(==|!=|>|<|>=|<=)\s*(.+)$/)
|
||||
if (comparisonMatch) {
|
||||
const [, leftExpr, operator, rightExpr] = comparisonMatch
|
||||
console.log(` - Left expression: "${leftExpr.trim()}"`)
|
||||
console.log(` - Operator: "${operator}"`)
|
||||
console.log(` - Right expression: "${rightExpr.trim()}"`)
|
||||
|
||||
// Parse right side (remove quotes if it's a string literal)
|
||||
let rightValue = rightExpr.trim()
|
||||
if (rightValue.startsWith('"') && rightValue.endsWith('"')) {
|
||||
rightValue = rightValue.slice(1, -1)
|
||||
}
|
||||
console.log(` - Right value: "${rightValue}" (type: ${typeof rightValue})`)
|
||||
|
||||
const conditionResult = leftResult === rightValue
|
||||
console.log(` - Condition result: ${conditionResult} (${leftResult} === ${rightValue})`)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ JSONPath evaluation failed:', error.message)
|
||||
}
|
||||
|
||||
// Step 4: Test workflow trigger matching logic
|
||||
console.log('\n🎯 Step 4: Testing trigger matching logic...')
|
||||
|
||||
for (const workflow of workflows.docs) {
|
||||
console.log(`\nChecking workflow: "${workflow.name}"`)
|
||||
|
||||
const triggers = workflow.triggers
|
||||
if (!triggers || !Array.isArray(triggers)) {
|
||||
console.log(' ❌ No triggers found')
|
||||
continue
|
||||
}
|
||||
|
||||
for (const trigger of triggers) {
|
||||
console.log(` Trigger details:`)
|
||||
console.log(` - Type: ${trigger.type}`)
|
||||
console.log(` - Collection: ${trigger.collection}`)
|
||||
console.log(` - CollectionSlug: ${trigger.collectionSlug}`)
|
||||
console.log(` - Operation: ${trigger.operation}`)
|
||||
console.log(` - Condition: ${trigger.condition}`)
|
||||
|
||||
// Check basic matching criteria
|
||||
const typeMatch = trigger.type === 'collection-trigger'
|
||||
const collectionMatch = trigger.collection === 'orders' || trigger.collectionSlug === 'orders'
|
||||
const operationMatch = trigger.operation === 'update'
|
||||
|
||||
console.log(` - Type match: ${typeMatch}`)
|
||||
console.log(` - Collection match: ${collectionMatch}`)
|
||||
console.log(` - Operation match: ${operationMatch}`)
|
||||
|
||||
if (typeMatch && collectionMatch && operationMatch) {
|
||||
console.log(` ✅ Basic trigger criteria match!`)
|
||||
|
||||
if (trigger.condition) {
|
||||
console.log(` Testing condition: ${trigger.condition}`)
|
||||
// Note: We'd need to call the actual evaluateCondition method here
|
||||
// but we're simulating the logic
|
||||
} else {
|
||||
console.log(` ✅ No condition required - this trigger should fire!`)
|
||||
}
|
||||
} else {
|
||||
console.log(` ❌ Basic trigger criteria don't match`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Update order and trace hook execution
|
||||
console.log('\n🔄 Step 5: Updating order status to trigger workflow...')
|
||||
|
||||
console.log('Before update - checking existing workflow runs:')
|
||||
const beforeRuns = await payload.find({
|
||||
collection: 'workflow-runs'
|
||||
})
|
||||
console.log(` Existing workflow runs: ${beforeRuns.docs.length}`)
|
||||
|
||||
console.log('\nUpdating order status to "Paid"...')
|
||||
const updatedOrder = await payload.update({
|
||||
collection: 'orders',
|
||||
id: testOrder.id,
|
||||
data: {
|
||||
status: 'Paid'
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Order updated successfully. New status: "${updatedOrder.status}"`)
|
||||
|
||||
// Wait a moment for async processing
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
|
||||
console.log('\nAfter update - checking for new workflow runs:')
|
||||
const afterRuns = await payload.find({
|
||||
collection: 'workflow-runs'
|
||||
})
|
||||
console.log(` Total workflow runs: ${afterRuns.docs.length}`)
|
||||
console.log(` New runs created: ${afterRuns.docs.length - beforeRuns.docs.length}`)
|
||||
|
||||
if (afterRuns.docs.length > beforeRuns.docs.length) {
|
||||
const newRuns = afterRuns.docs.slice(0, afterRuns.docs.length - beforeRuns.docs.length)
|
||||
for (const run of newRuns) {
|
||||
console.log(` - Run ID: ${run.id}`)
|
||||
console.log(` - Workflow: ${run.workflow}`)
|
||||
console.log(` - Status: ${run.status}`)
|
||||
console.log(` - Context: ${JSON.stringify(run.context, null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: Check job queue
|
||||
console.log('\n⚙️ Step 6: Checking job queue...')
|
||||
const jobs = await payload.find({
|
||||
collection: 'payload-jobs',
|
||||
sort: '-createdAt',
|
||||
limit: 10
|
||||
})
|
||||
|
||||
console.log(`Recent jobs in queue: ${jobs.docs.length}`)
|
||||
for (const job of jobs.docs.slice(0, 5)) {
|
||||
console.log(` - Job ${job.id}: ${job.taskSlug} (${job.processingError ? 'ERROR' : 'OK'})`)
|
||||
}
|
||||
|
||||
console.log('\n✨ Debugging complete!')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
debugWorkflowExecution().catch(console.error)
|
||||
177
debug-workflow-executor-patch.js
Normal file
177
debug-workflow-executor-patch.js
Normal file
@@ -0,0 +1,177 @@
|
||||
// Enhanced debugging patch for workflow executor
|
||||
// This temporarily patches the workflow executor to add comprehensive logging
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
|
||||
async function patchAndTestWorkflow() {
|
||||
const payload = await getPayload({
|
||||
config: (await import('./dev/payload.config.ts')).default
|
||||
})
|
||||
|
||||
console.log('🔧 === COMPREHENSIVE WORKFLOW DEBUGGING ===')
|
||||
|
||||
// Step 1: Check workflow collection structure and versioning
|
||||
console.log('\n📋 Step 1: Analyzing workflow collection configuration...')
|
||||
|
||||
const workflowCollection = payload.collections.workflows
|
||||
console.log('Workflow collection config:')
|
||||
console.log(' - Slug:', workflowCollection.config.slug)
|
||||
console.log(' - Versions enabled:', !!workflowCollection.config.versions)
|
||||
console.log(' - Drafts enabled:', !!workflowCollection.config.versions?.drafts)
|
||||
|
||||
// Step 2: Test different query approaches for workflows
|
||||
console.log('\n🔍 Step 2: Testing workflow queries...')
|
||||
|
||||
// Query 1: Default query (what the plugin currently uses)
|
||||
console.log('Query 1: Default query (no status filter)')
|
||||
try {
|
||||
const workflows1 = await payload.find({
|
||||
collection: 'workflows',
|
||||
depth: 2,
|
||||
limit: 100
|
||||
})
|
||||
console.log(` - Found: ${workflows1.docs.length} workflows`)
|
||||
for (const wf of workflows1.docs) {
|
||||
console.log(` - "${wf.name}" (ID: ${wf.id}) Status: ${wf._status || 'no-status'}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` - Error: ${error.message}`)
|
||||
}
|
||||
|
||||
// Query 2: Only published workflows
|
||||
console.log('\nQuery 2: Only published workflows')
|
||||
try {
|
||||
const workflows2 = await payload.find({
|
||||
collection: 'workflows',
|
||||
depth: 2,
|
||||
limit: 100,
|
||||
where: {
|
||||
_status: {
|
||||
equals: 'published'
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(` - Found: ${workflows2.docs.length} published workflows`)
|
||||
for (const wf of workflows2.docs) {
|
||||
console.log(` - "${wf.name}" (ID: ${wf.id}) Status: ${wf._status}`)
|
||||
console.log(` Triggers: ${JSON.stringify(wf.triggers, null, 2)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` - Error: ${error.message}`)
|
||||
}
|
||||
|
||||
// Query 3: All workflows with explicit status
|
||||
console.log('\nQuery 3: All workflows with status field')
|
||||
try {
|
||||
const workflows3 = await payload.find({
|
||||
collection: 'workflows',
|
||||
depth: 2,
|
||||
limit: 100,
|
||||
where: {
|
||||
_status: {
|
||||
exists: true
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(` - Found: ${workflows3.docs.length} workflows with status`)
|
||||
for (const wf of workflows3.docs) {
|
||||
console.log(` - "${wf.name}" Status: ${wf._status}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` - Error: ${error.message}`)
|
||||
}
|
||||
|
||||
// Step 3: Create a test order and manually trigger the evaluation
|
||||
console.log('\n📦 Step 3: Creating test order...')
|
||||
|
||||
const testOrder = await payload.create({
|
||||
collection: 'orders',
|
||||
data: {
|
||||
orderName: 'Debug Comprehensive Test - ' + Date.now(),
|
||||
status: 'Unpaid',
|
||||
customerEmail: 'debug@example.com',
|
||||
totalPrice: 1000,
|
||||
items: [{
|
||||
name: 'Debug Item',
|
||||
quantity: 1,
|
||||
price: 1000
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`Created order: ${testOrder.id} with status: ${testOrder.status}`)
|
||||
|
||||
// Step 4: Test the WorkflowExecutor.executeTriggeredWorkflows method directly
|
||||
console.log('\n🎯 Step 4: Testing executeTriggeredWorkflows directly...')
|
||||
|
||||
// Access the workflow executor (this might require accessing internal plugin state)
|
||||
// For now, let's simulate what should happen
|
||||
|
||||
console.log('Simulating executeTriggeredWorkflows call...')
|
||||
console.log(' - Collection: orders')
|
||||
console.log(' - Operation: update')
|
||||
console.log(' - Doc: { ...order, status: "Paid" }')
|
||||
console.log(' - PreviousDoc:', JSON.stringify(testOrder, null, 2))
|
||||
|
||||
// Step 5: Update the order and capture all logs
|
||||
console.log('\n🔄 Step 5: Updating order with comprehensive logging...')
|
||||
|
||||
// First, let's check what hooks are actually registered
|
||||
const orderCollection = payload.collections.orders
|
||||
console.log('Order collection hooks:')
|
||||
console.log(' - afterChange hooks:', orderCollection.config.hooks?.afterChange?.length || 0)
|
||||
|
||||
// Count current workflow runs before
|
||||
const beforeRuns = await payload.find({ collection: 'workflow-runs' })
|
||||
console.log(`Current workflow runs: ${beforeRuns.docs.length}`)
|
||||
|
||||
// Update the order
|
||||
console.log('\nUpdating order status to "Paid"...')
|
||||
const updatedOrder = await payload.update({
|
||||
collection: 'orders',
|
||||
id: testOrder.id,
|
||||
data: { status: 'Paid' }
|
||||
})
|
||||
|
||||
console.log(`Order updated: ${updatedOrder.status}`)
|
||||
|
||||
// Wait and check for workflow runs
|
||||
console.log('Waiting 5 seconds for async processing...')
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
|
||||
const afterRuns = await payload.find({ collection: 'workflow-runs' })
|
||||
console.log(`Workflow runs after: ${afterRuns.docs.length}`)
|
||||
console.log(`New runs created: ${afterRuns.docs.length - beforeRuns.docs.length}`)
|
||||
|
||||
if (afterRuns.docs.length > beforeRuns.docs.length) {
|
||||
console.log('✅ New workflow runs found!')
|
||||
const newRuns = afterRuns.docs.slice(0, afterRuns.docs.length - beforeRuns.docs.length)
|
||||
for (const run of newRuns) {
|
||||
console.log(` - Run ${run.id}: ${run.status}`)
|
||||
}
|
||||
} else {
|
||||
console.log('❌ No new workflow runs created')
|
||||
|
||||
// Additional debugging
|
||||
console.log('\n🕵️ Deep debugging - checking plugin state...')
|
||||
|
||||
// Check if the plugin is actually loaded
|
||||
console.log('Available collections:', Object.keys(payload.collections))
|
||||
|
||||
// Check for recent jobs
|
||||
const recentJobs = await payload.find({
|
||||
collection: 'payload-jobs',
|
||||
sort: '-createdAt',
|
||||
limit: 5
|
||||
})
|
||||
console.log(`Recent jobs: ${recentJobs.docs.length}`)
|
||||
for (const job of recentJobs.docs) {
|
||||
console.log(` - ${job.taskSlug} (${job.processingError ? 'ERROR' : 'OK'})`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✨ Comprehensive debugging complete!')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
patchAndTestWorkflow().catch(console.error)
|
||||
@@ -103,7 +103,8 @@ const buildConfigWithMemoryDB = async () => {
|
||||
plugins: [
|
||||
workflowsPlugin<CollectionSlug>({
|
||||
collectionTriggers: {
|
||||
posts: true
|
||||
posts: true,
|
||||
media: true
|
||||
},
|
||||
steps: [
|
||||
HttpRequestStepTask,
|
||||
|
||||
153
diagnostic-hooks.js
Normal file
153
diagnostic-hooks.js
Normal file
@@ -0,0 +1,153 @@
|
||||
// Comprehensive diagnostic script for hook registration issues
|
||||
// This should be run in your actual PayloadCMS environment
|
||||
|
||||
console.log('🔍 === COMPREHENSIVE HOOK DIAGNOSTIC ===')
|
||||
|
||||
// STEP 1: Add this to your payload.config.ts or wherever you initialize PayloadCMS
|
||||
console.log(`
|
||||
📋 STEP 1: Add this diagnostic code to your PayloadCMS initialization:
|
||||
|
||||
// After PayloadCMS is initialized, run this diagnostic
|
||||
const diagnostic = async () => {
|
||||
console.log('🔍 === HOOK REGISTRATION DIAGNOSTIC ===')
|
||||
|
||||
// Check if orders collection exists
|
||||
const ordersCollection = payload.collections.orders
|
||||
if (!ordersCollection) {
|
||||
console.log('❌ CRITICAL: orders collection not found!')
|
||||
console.log('Available collections:', Object.keys(payload.collections))
|
||||
return
|
||||
}
|
||||
|
||||
console.log('✅ orders collection found')
|
||||
|
||||
// Check hooks on orders collection
|
||||
const hooks = ordersCollection.config.hooks
|
||||
console.log('Orders collection hooks:')
|
||||
console.log(' - afterChange:', hooks?.afterChange?.length || 0)
|
||||
console.log(' - afterRead:', hooks?.afterRead?.length || 0)
|
||||
console.log(' - afterDelete:', hooks?.afterDelete?.length || 0)
|
||||
|
||||
// If no hooks, something is wrong
|
||||
if (!hooks?.afterChange || hooks.afterChange.length === 0) {
|
||||
console.log('❌ CRITICAL: No afterChange hooks registered on orders collection!')
|
||||
console.log('This means the automation plugin hook registration failed.')
|
||||
return
|
||||
}
|
||||
|
||||
// Test hook execution by manually calling them
|
||||
console.log('\\n🧪 Testing hook execution manually...')
|
||||
|
||||
const testDoc = {
|
||||
id: 'test-' + Date.now(),
|
||||
orderName: 'Test Order',
|
||||
status: 'Paid',
|
||||
customerEmail: 'test@example.com',
|
||||
totalPrice: 1000
|
||||
}
|
||||
|
||||
const previousDoc = {
|
||||
...testDoc,
|
||||
status: 'Unpaid'
|
||||
}
|
||||
|
||||
// Create a mock change object
|
||||
const mockChange = {
|
||||
collection: { slug: 'orders' },
|
||||
operation: 'update',
|
||||
doc: testDoc,
|
||||
previousDoc: previousDoc,
|
||||
req: {} // minimal request object
|
||||
}
|
||||
|
||||
console.log('Calling hooks manually with test data...')
|
||||
|
||||
for (let i = 0; i < hooks.afterChange.length; i++) {
|
||||
try {
|
||||
console.log(\`Calling hook #\${i + 1}...\`)
|
||||
await hooks.afterChange[i](mockChange)
|
||||
console.log(\`✅ Hook #\${i + 1} completed\`)
|
||||
} catch (error) {
|
||||
console.log(\`❌ Hook #\${i + 1} failed:\`, error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run diagnostic after PayloadCMS is fully initialized
|
||||
setTimeout(diagnostic, 2000)
|
||||
`)
|
||||
|
||||
console.log(`
|
||||
📋 STEP 2: Check your plugin configuration
|
||||
|
||||
Verify your payload.config.ts includes the orders collection:
|
||||
|
||||
automationPlugin({
|
||||
collections: ['orders'], // ← MUST include 'orders'
|
||||
// ... other config
|
||||
})
|
||||
|
||||
NOT:
|
||||
automationPlugin({
|
||||
collections: ['users', 'products'], // ← Missing 'orders'!
|
||||
})
|
||||
`)
|
||||
|
||||
console.log(`
|
||||
📋 STEP 3: Alternative hook registration test
|
||||
|
||||
Add this to your order update code to manually verify hooks:
|
||||
|
||||
// Before updating the order
|
||||
console.log('🔍 Pre-update hook check:')
|
||||
const orderCollection = payload.collections.orders
|
||||
console.log('afterChange hooks count:', orderCollection.config.hooks?.afterChange?.length)
|
||||
|
||||
// Update the order
|
||||
const result = await payload.update({...})
|
||||
|
||||
// Check for workflow runs immediately
|
||||
const runs = await payload.find({ collection: 'workflow-runs' })
|
||||
console.log('Workflow runs after update:', runs.docs.length)
|
||||
`)
|
||||
|
||||
console.log(`
|
||||
📋 STEP 4: Most likely root causes
|
||||
|
||||
1. Plugin Configuration Issue:
|
||||
- 'orders' not included in collections array
|
||||
- Plugin disabled or not properly applied
|
||||
|
||||
2. Collection Name Mismatch:
|
||||
- Your collection might be named differently (e.g., 'order' vs 'orders')
|
||||
- Case sensitivity issue
|
||||
|
||||
3. Hook Registration Timing:
|
||||
- Plugin hooks registered before collection is fully initialized
|
||||
- Race condition in PayloadCMS startup
|
||||
|
||||
4. Development Environment Issue:
|
||||
- Hot reloading interfering with hook registration
|
||||
- Multiple PayloadCMS instances
|
||||
|
||||
5. Database/Collection Issue:
|
||||
- Collection doesn't exist in database
|
||||
- Collection configuration mismatch
|
||||
`)
|
||||
|
||||
console.log(`
|
||||
🆘 QUICK DEBUG COMMANDS
|
||||
|
||||
Run these in your browser console or Node.js environment:
|
||||
|
||||
// Check available collections
|
||||
Object.keys(payload.collections)
|
||||
|
||||
// Check specific collection hooks
|
||||
payload.collections.orders?.config?.hooks?.afterChange?.length
|
||||
|
||||
// Check plugin configuration (if accessible)
|
||||
// This depends on how your config is structured
|
||||
`)
|
||||
|
||||
process.exit(0)
|
||||
130
hook-verification-test.js
Normal file
130
hook-verification-test.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// Hook verification test - run this in your PayloadCMS environment
|
||||
// This will help identify why registered hooks aren't executing
|
||||
|
||||
console.log('🔍 === HOOK VERIFICATION TEST ===')
|
||||
|
||||
console.log(`
|
||||
Add this code to your PayloadCMS environment after initialization:
|
||||
|
||||
const verifyHooks = async () => {
|
||||
console.log('🔍 === HOOK VERIFICATION DIAGNOSTIC ===')
|
||||
|
||||
// 1. Check if hooks are still registered
|
||||
const ordersCollection = payload.collections.orders
|
||||
const hooks = ordersCollection.config.hooks.afterChange || []
|
||||
|
||||
console.log('Hook count:', hooks.length)
|
||||
console.log('Hook types:', hooks.map((h, i) => \`#\${i}: \${typeof h}\`))
|
||||
|
||||
// 2. Check if hooks are actually functions
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
const hook = hooks[i]
|
||||
console.log(\`Hook #\${i}:\`)
|
||||
console.log(\` - Type: \${typeof hook}\`)
|
||||
console.log(\` - Is Function: \${typeof hook === 'function'}\`)
|
||||
console.log(\` - Has Name: \${hook.name || 'anonymous'}\`)
|
||||
console.log(\` - String Preview: \${hook.toString().substring(0, 100)}...\`)
|
||||
}
|
||||
|
||||
// 3. Try to manually execute each hook
|
||||
console.log('\\n🧪 MANUAL HOOK EXECUTION TEST')
|
||||
|
||||
const mockChange = {
|
||||
collection: { slug: 'orders' },
|
||||
operation: 'update',
|
||||
doc: {
|
||||
id: 'test-' + Date.now(),
|
||||
orderName: 'Test Order',
|
||||
status: 'Paid',
|
||||
customerEmail: 'test@example.com'
|
||||
},
|
||||
previousDoc: {
|
||||
id: 'test-' + Date.now(),
|
||||
orderName: 'Test Order',
|
||||
status: 'Unpaid',
|
||||
customerEmail: 'test@example.com'
|
||||
},
|
||||
req: { user: null } // Minimal request object
|
||||
}
|
||||
|
||||
for (let i = 0; i < hooks.length; i++) {
|
||||
try {
|
||||
console.log(\`\\nTesting hook #\${i}...\`)
|
||||
console.log('About to call hook with mock data')
|
||||
|
||||
const result = await hooks[i](mockChange)
|
||||
|
||||
console.log(\`✅ Hook #\${i} executed successfully\`)
|
||||
console.log('Result:', result)
|
||||
|
||||
} catch (error) {
|
||||
console.log(\`❌ Hook #\${i} failed:\`)
|
||||
console.log('Error:', error.message)
|
||||
console.log('Stack:', error.stack)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check if hooks are being called during real operations
|
||||
console.log('\\n🔍 REAL OPERATION TEST')
|
||||
console.log('Creating a test order to see if hooks fire...')
|
||||
|
||||
// Add a simple test hook to verify hook execution
|
||||
const testHook = async (change) => {
|
||||
console.log('🚨 TEST HOOK FIRED! 🚨')
|
||||
console.log('Collection:', change.collection.slug)
|
||||
console.log('Operation:', change.operation)
|
||||
}
|
||||
|
||||
// Add test hook at the beginning
|
||||
ordersCollection.config.hooks.afterChange.unshift(testHook)
|
||||
console.log('Added test hook at position 0')
|
||||
|
||||
try {
|
||||
const testOrder = await payload.create({
|
||||
collection: 'orders',
|
||||
data: {
|
||||
orderName: 'Hook Verification Test',
|
||||
status: 'Unpaid',
|
||||
customerEmail: 'hooktest@example.com',
|
||||
totalPrice: 1000,
|
||||
items: [{ name: 'Test Item', quantity: 1, price: 1000 }]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Test order created:', testOrder.id)
|
||||
|
||||
// Update the order to trigger hooks
|
||||
const updatedOrder = await payload.update({
|
||||
collection: 'orders',
|
||||
id: testOrder.id,
|
||||
data: { status: 'Paid' }
|
||||
})
|
||||
|
||||
console.log('Test order updated to:', updatedOrder.status)
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error during test operation:', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Run after PayloadCMS is initialized
|
||||
setTimeout(verifyHooks, 3000)
|
||||
`)
|
||||
|
||||
console.log(`
|
||||
🎯 Expected Results:
|
||||
|
||||
If you see "🚨 TEST HOOK FIRED! 🚨" but NOT the automation plugin messages:
|
||||
- Hook execution works, but the automation plugin hook has an issue
|
||||
- Likely problem: Hook function malformed or has runtime error
|
||||
|
||||
If you DON'T see "🚨 TEST HOOK FIRED! 🚨":
|
||||
- Hook execution is completely broken
|
||||
- PayloadCMS configuration or timing issue
|
||||
|
||||
If hooks execute manually but not during real operations:
|
||||
- Hook registration timing issue
|
||||
- PayloadCMS lifecycle problem
|
||||
`)
|
||||
|
||||
process.exit(0)
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@xtr-dev/payload-workflows",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.21",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@xtr-dev/payload-workflows",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.21",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xtr-dev/payload-automation",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.21",
|
||||
"description": "PayloadCMS Automation Plugin - Comprehensive workflow automation system with visual workflow building, execution tracking, and step types",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -480,7 +480,7 @@ export class WorkflowExecutor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a condition using JSONPath
|
||||
* Evaluate a condition using JSONPath and comparison operators
|
||||
*/
|
||||
public evaluateCondition(condition: string, context: ExecutionContext): boolean {
|
||||
this.logger.debug({
|
||||
@@ -492,34 +492,94 @@ export class WorkflowExecutor {
|
||||
}, 'Starting condition evaluation')
|
||||
|
||||
try {
|
||||
const result = JSONPath({
|
||||
json: context,
|
||||
path: condition,
|
||||
wrap: false
|
||||
})
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
result,
|
||||
resultType: Array.isArray(result) ? 'array' : typeof result,
|
||||
resultLength: Array.isArray(result) ? result.length : undefined
|
||||
}, 'JSONPath evaluation result')
|
||||
|
||||
// Handle different result types
|
||||
let finalResult: boolean
|
||||
if (Array.isArray(result)) {
|
||||
finalResult = result.length > 0 && Boolean(result[0])
|
||||
// Check if this is a comparison expression
|
||||
const comparisonMatch = condition.match(/^(.+?)\s*(==|!=|>|<|>=|<=)\s*(.+)$/)
|
||||
|
||||
if (comparisonMatch) {
|
||||
const [, leftExpr, operator, rightExpr] = comparisonMatch
|
||||
|
||||
// Evaluate left side (should be JSONPath)
|
||||
const leftValue = this.resolveJSONPathValue(leftExpr.trim(), context)
|
||||
|
||||
// Parse right side (could be string, number, boolean, or JSONPath)
|
||||
const rightValue = this.parseConditionValue(rightExpr.trim(), context)
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
leftExpr: leftExpr.trim(),
|
||||
leftValue,
|
||||
operator,
|
||||
rightExpr: rightExpr.trim(),
|
||||
rightValue,
|
||||
leftType: typeof leftValue,
|
||||
rightType: typeof rightValue
|
||||
}, 'Evaluating comparison condition')
|
||||
|
||||
// Perform comparison
|
||||
let result: boolean
|
||||
switch (operator) {
|
||||
case '==':
|
||||
result = leftValue === rightValue
|
||||
break
|
||||
case '!=':
|
||||
result = leftValue !== rightValue
|
||||
break
|
||||
case '>':
|
||||
result = Number(leftValue) > Number(rightValue)
|
||||
break
|
||||
case '<':
|
||||
result = Number(leftValue) < Number(rightValue)
|
||||
break
|
||||
case '>=':
|
||||
result = Number(leftValue) >= Number(rightValue)
|
||||
break
|
||||
case '<=':
|
||||
result = Number(leftValue) <= Number(rightValue)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Unknown comparison operator: ${operator}`)
|
||||
}
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
result,
|
||||
leftValue,
|
||||
rightValue,
|
||||
operator
|
||||
}, 'Comparison condition evaluation completed')
|
||||
|
||||
return result
|
||||
} else {
|
||||
finalResult = Boolean(result)
|
||||
// Treat as simple JSONPath boolean evaluation
|
||||
const result = JSONPath({
|
||||
json: context,
|
||||
path: condition,
|
||||
wrap: false
|
||||
})
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
result,
|
||||
resultType: Array.isArray(result) ? 'array' : typeof result,
|
||||
resultLength: Array.isArray(result) ? result.length : undefined
|
||||
}, 'JSONPath boolean evaluation result')
|
||||
|
||||
// Handle different result types
|
||||
let finalResult: boolean
|
||||
if (Array.isArray(result)) {
|
||||
finalResult = result.length > 0 && Boolean(result[0])
|
||||
} else {
|
||||
finalResult = Boolean(result)
|
||||
}
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
finalResult,
|
||||
originalResult: result
|
||||
}, 'Boolean condition evaluation completed')
|
||||
|
||||
return finalResult
|
||||
}
|
||||
|
||||
this.logger.debug({
|
||||
condition,
|
||||
finalResult,
|
||||
originalResult: result
|
||||
}, 'Condition evaluation completed')
|
||||
|
||||
return finalResult
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
condition,
|
||||
@@ -531,6 +591,49 @@ export class WorkflowExecutor {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a JSONPath value from the context
|
||||
*/
|
||||
private resolveJSONPathValue(expr: string, context: ExecutionContext): any {
|
||||
if (expr.startsWith('$')) {
|
||||
const result = JSONPath({
|
||||
json: context,
|
||||
path: expr,
|
||||
wrap: false
|
||||
})
|
||||
// Return first result if array, otherwise the result itself
|
||||
return Array.isArray(result) && result.length > 0 ? result[0] : result
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a condition value (string literal, number, boolean, or JSONPath)
|
||||
*/
|
||||
private parseConditionValue(expr: string, context: ExecutionContext): any {
|
||||
// Handle string literals
|
||||
if ((expr.startsWith('"') && expr.endsWith('"')) || (expr.startsWith("'") && expr.endsWith("'"))) {
|
||||
return expr.slice(1, -1) // Remove quotes
|
||||
}
|
||||
|
||||
// Handle boolean literals
|
||||
if (expr === 'true') return true
|
||||
if (expr === 'false') return false
|
||||
|
||||
// Handle number literals
|
||||
if (/^-?\d+(\.\d+)?$/.test(expr)) {
|
||||
return Number(expr)
|
||||
}
|
||||
|
||||
// Handle JSONPath expressions
|
||||
if (expr.startsWith('$')) {
|
||||
return this.resolveJSONPathValue(expr, context)
|
||||
}
|
||||
|
||||
// Return as string if nothing else matches
|
||||
return expr
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a workflow with the given context
|
||||
@@ -555,19 +658,48 @@ export class WorkflowExecutor {
|
||||
}
|
||||
})
|
||||
|
||||
this.logger.info({
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name,
|
||||
contextSummary: {
|
||||
triggerType: context.trigger.type,
|
||||
triggerCollection: context.trigger.collection,
|
||||
triggerOperation: context.trigger.operation,
|
||||
hasDoc: !!context.trigger.doc,
|
||||
userEmail: context.trigger.req?.user?.email
|
||||
}
|
||||
}, 'About to create workflow run record')
|
||||
|
||||
// Create a workflow run record
|
||||
const workflowRun = await this.payload.create({
|
||||
collection: 'workflow-runs',
|
||||
data: {
|
||||
context: serializeContext(),
|
||||
startedAt: new Date().toISOString(),
|
||||
status: 'running',
|
||||
triggeredBy: context.trigger.req?.user?.email || 'system',
|
||||
workflow: workflow.id,
|
||||
workflowVersion: 1 // Default version since generated type doesn't have _version field
|
||||
},
|
||||
req
|
||||
})
|
||||
let workflowRun;
|
||||
try {
|
||||
workflowRun = await this.payload.create({
|
||||
collection: 'workflow-runs',
|
||||
data: {
|
||||
context: serializeContext(),
|
||||
startedAt: new Date().toISOString(),
|
||||
status: 'running',
|
||||
triggeredBy: context.trigger.req?.user?.email || 'system',
|
||||
workflow: workflow.id,
|
||||
workflowVersion: 1 // Default version since generated type doesn't have _version field
|
||||
},
|
||||
req
|
||||
})
|
||||
|
||||
this.logger.info({
|
||||
workflowRunId: workflowRun.id,
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name
|
||||
}, 'Workflow run record created successfully')
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
errorStack: error instanceof Error ? error.stack : undefined,
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name
|
||||
}, 'Failed to create workflow run record')
|
||||
throw error
|
||||
}
|
||||
|
||||
try {
|
||||
// Resolve execution order based on dependencies
|
||||
@@ -655,6 +787,13 @@ export class WorkflowExecutor {
|
||||
previousDoc: unknown,
|
||||
req: PayloadRequest
|
||||
): Promise<void> {
|
||||
console.log('🚨 EXECUTOR: executeTriggeredWorkflows called!')
|
||||
console.log('🚨 EXECUTOR: Collection =', collection)
|
||||
console.log('🚨 EXECUTOR: Operation =', operation)
|
||||
console.log('🚨 EXECUTOR: Doc ID =', (doc as any)?.id)
|
||||
console.log('🚨 EXECUTOR: Has payload?', !!this.payload)
|
||||
console.log('🚨 EXECUTOR: Has logger?', !!this.logger)
|
||||
|
||||
this.logger.info({
|
||||
collection,
|
||||
operation,
|
||||
@@ -711,6 +850,18 @@ export class WorkflowExecutor {
|
||||
}, 'Matching triggers found')
|
||||
|
||||
for (const trigger of matchingTriggers) {
|
||||
this.logger.info({
|
||||
workflowId: workflow.id,
|
||||
workflowName: workflow.name,
|
||||
triggerDetails: {
|
||||
type: trigger.type,
|
||||
collection: trigger.collection,
|
||||
collectionSlug: trigger.collectionSlug,
|
||||
operation: trigger.operation,
|
||||
hasCondition: !!trigger.condition
|
||||
}
|
||||
}, 'Processing matching trigger - about to execute workflow')
|
||||
|
||||
// Create execution context for condition evaluation
|
||||
const context: ExecutionContext = {
|
||||
steps: {},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {Config} from 'payload'
|
||||
|
||||
import type {WorkflowsPluginConfig} from "./config-types.js"
|
||||
import type {WorkflowsPluginConfig, CollectionTriggerConfigCrud} from "./config-types.js"
|
||||
|
||||
import {createWorkflowCollection} from '../collections/Workflow.js'
|
||||
import {WorkflowRunsCollection} from '../collections/WorkflowRuns.js'
|
||||
@@ -15,6 +15,24 @@ import {getConfigLogger, initializeLogger} from './logger.js'
|
||||
|
||||
export {getLogger} from './logger.js'
|
||||
|
||||
// Global executor registry for config-phase hooks
|
||||
let globalExecutor: WorkflowExecutor | null = null
|
||||
|
||||
const setWorkflowExecutor = (executor: WorkflowExecutor) => {
|
||||
console.log('🚨 SETTING GLOBAL EXECUTOR')
|
||||
globalExecutor = executor
|
||||
|
||||
// Also set on global object as fallback
|
||||
if (typeof global !== 'undefined') {
|
||||
(global as any).__workflowExecutor = executor
|
||||
console.log('🚨 EXECUTOR ALSO SET ON GLOBAL OBJECT')
|
||||
}
|
||||
}
|
||||
|
||||
const getWorkflowExecutor = (): WorkflowExecutor | null => {
|
||||
return globalExecutor
|
||||
}
|
||||
|
||||
const applyCollectionsConfig = <T extends string>(pluginOptions: WorkflowsPluginConfig<T>, config: Config) => {
|
||||
// Add workflow collections
|
||||
if (!config.collections) {
|
||||
@@ -27,8 +45,8 @@ const applyCollectionsConfig = <T extends string>(pluginOptions: WorkflowsPlugin
|
||||
)
|
||||
}
|
||||
|
||||
// Track if hooks have been initialized to prevent double registration
|
||||
let hooksInitialized = false
|
||||
// Removed config-phase hook registration - user collections don't exist during config phase
|
||||
|
||||
|
||||
export const workflowsPlugin =
|
||||
<TSlug extends string>(pluginOptions: WorkflowsPluginConfig<TSlug>) =>
|
||||
@@ -39,6 +57,92 @@ export const workflowsPlugin =
|
||||
}
|
||||
|
||||
applyCollectionsConfig<TSlug>(pluginOptions, config)
|
||||
|
||||
// CRITICAL: Modify existing collection configs BEFORE PayloadCMS processes them
|
||||
// This is the ONLY time we can add hooks that will actually work
|
||||
const logger = getConfigLogger()
|
||||
logger.info('Attempting to modify collection configs before PayloadCMS initialization...')
|
||||
|
||||
if (config.collections && pluginOptions.collectionTriggers) {
|
||||
for (const [triggerSlug, triggerConfig] of Object.entries(pluginOptions.collectionTriggers)) {
|
||||
if (!triggerConfig) continue
|
||||
|
||||
// Find the collection config that matches
|
||||
const collectionIndex = config.collections.findIndex(c => c.slug === triggerSlug)
|
||||
if (collectionIndex === -1) {
|
||||
logger.warn(`Collection '${triggerSlug}' not found in config.collections`)
|
||||
continue
|
||||
}
|
||||
|
||||
const collection = config.collections[collectionIndex]
|
||||
logger.info(`Found collection '${triggerSlug}' - modifying its hooks...`)
|
||||
|
||||
// Initialize hooks if needed
|
||||
if (!collection.hooks) {
|
||||
collection.hooks = {}
|
||||
}
|
||||
if (!collection.hooks.afterChange) {
|
||||
collection.hooks.afterChange = []
|
||||
}
|
||||
|
||||
// Create a properly bound hook function that doesn't rely on closures
|
||||
// Use a simple function that PayloadCMS can definitely execute
|
||||
const automationHook = Object.assign(
|
||||
async function payloadAutomationHook(args: any) {
|
||||
try {
|
||||
// Use global console to ensure output
|
||||
global.console.log('🔥🔥🔥 AUTOMATION HOOK EXECUTED! 🔥🔥🔥')
|
||||
global.console.log('Collection:', args?.collection?.slug)
|
||||
global.console.log('Operation:', args?.operation)
|
||||
global.console.log('Doc ID:', args?.doc?.id)
|
||||
|
||||
// Try multiple ways to get the executor
|
||||
let executor = null
|
||||
|
||||
// Method 1: Global registry
|
||||
if (typeof getWorkflowExecutor === 'function') {
|
||||
executor = getWorkflowExecutor()
|
||||
}
|
||||
|
||||
// Method 2: Global variable fallback
|
||||
if (!executor && typeof global !== 'undefined' && (global as any).__workflowExecutor) {
|
||||
executor = (global as any).__workflowExecutor
|
||||
global.console.log('Got executor from global variable')
|
||||
}
|
||||
|
||||
if (executor) {
|
||||
global.console.log('✅ Executor found - executing workflows!')
|
||||
await executor.executeTriggeredWorkflows(
|
||||
args.collection.slug,
|
||||
args.operation,
|
||||
args.doc,
|
||||
args.previousDoc,
|
||||
args.req
|
||||
)
|
||||
global.console.log('✅ Workflow execution completed!')
|
||||
} else {
|
||||
global.console.log('⚠️ No executor available')
|
||||
}
|
||||
} catch (error) {
|
||||
global.console.error('❌ Hook execution error:', error)
|
||||
// Don't throw - just log
|
||||
}
|
||||
|
||||
// Always return undefined to match other hooks
|
||||
return undefined
|
||||
},
|
||||
{
|
||||
// Add metadata to help debugging
|
||||
__isAutomationHook: true,
|
||||
__version: '0.0.21'
|
||||
}
|
||||
)
|
||||
|
||||
// Add the hook to the collection config
|
||||
collection.hooks.afterChange.push(automationHook)
|
||||
logger.info(`Added automation hook to '${triggerSlug}' - hook count: ${collection.hooks.afterChange.length}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.jobs) {
|
||||
config.jobs = {tasks: []}
|
||||
@@ -65,13 +169,7 @@ export const workflowsPlugin =
|
||||
// Set up onInit to register collection hooks and initialize features
|
||||
const incomingOnInit = config.onInit
|
||||
config.onInit = async (payload) => {
|
||||
configLogger.info(`onInit called - hooks already initialized: ${hooksInitialized}, collections: ${Object.keys(payload.collections).length}`)
|
||||
|
||||
// Prevent double initialization in dev mode
|
||||
if (hooksInitialized) {
|
||||
configLogger.warn('Hooks already initialized, skipping to prevent duplicate registration')
|
||||
return
|
||||
}
|
||||
configLogger.info(`onInit called - collections: ${Object.keys(payload.collections).length}`)
|
||||
|
||||
// Execute any existing onInit functions first
|
||||
if (incomingOnInit) {
|
||||
@@ -87,11 +185,16 @@ export const workflowsPlugin =
|
||||
logger.info(`Plugin configuration: ${Object.keys(pluginOptions.collectionTriggers || {}).length} collection triggers, ${pluginOptions.steps?.length || 0} steps`)
|
||||
|
||||
// Create workflow executor instance
|
||||
console.log('🚨 CREATING WORKFLOW EXECUTOR INSTANCE')
|
||||
const executor = new WorkflowExecutor(payload, logger)
|
||||
console.log('🚨 EXECUTOR CREATED:', typeof executor)
|
||||
console.log('🚨 EXECUTOR METHODS:', Object.getOwnPropertyNames(Object.getPrototypeOf(executor)))
|
||||
|
||||
// Register executor globally
|
||||
setWorkflowExecutor(executor)
|
||||
|
||||
// Initialize hooks
|
||||
logger.info('Initializing collection hooks...')
|
||||
initCollectionHooks(pluginOptions, payload, logger, executor)
|
||||
// Hooks are now registered during config phase - just log status
|
||||
logger.info('Hooks were registered during config phase - executor now available')
|
||||
|
||||
logger.info('Initializing global hooks...')
|
||||
initGlobalHooks(payload, logger, executor)
|
||||
@@ -107,7 +210,6 @@ export const workflowsPlugin =
|
||||
await registerCronJobs(payload, logger)
|
||||
|
||||
logger.info('Plugin initialized successfully - all hooks registered')
|
||||
hooksInitialized = true
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
@@ -39,19 +39,55 @@ export function initCollectionHooks<T extends string>(pluginOptions: WorkflowsPl
|
||||
collection.config.hooks.afterChange = collection.config.hooks.afterChange || []
|
||||
collection.config.hooks.afterChange.push(async (change) => {
|
||||
const operation = change.operation as 'create' | 'update'
|
||||
logger.debug({
|
||||
|
||||
// AGGRESSIVE LOGGING - this should ALWAYS appear
|
||||
console.log('🚨 AUTOMATION PLUGIN HOOK CALLED! 🚨')
|
||||
console.log('Collection:', change.collection.slug)
|
||||
console.log('Operation:', operation)
|
||||
console.log('Doc ID:', change.doc?.id)
|
||||
console.log('Has executor?', !!executor)
|
||||
console.log('Executor type:', typeof executor)
|
||||
|
||||
logger.info({
|
||||
slug: change.collection.slug,
|
||||
operation,
|
||||
}, 'Collection hook triggered')
|
||||
docId: change.doc?.id,
|
||||
previousDocId: change.previousDoc?.id,
|
||||
hasExecutor: !!executor,
|
||||
executorType: typeof executor
|
||||
}, 'AUTOMATION PLUGIN: Collection hook triggered')
|
||||
|
||||
// Execute workflows for this trigger
|
||||
await executor.executeTriggeredWorkflows(
|
||||
change.collection.slug,
|
||||
operation,
|
||||
change.doc,
|
||||
change.previousDoc,
|
||||
change.req
|
||||
)
|
||||
try {
|
||||
console.log('🚨 About to call executeTriggeredWorkflows')
|
||||
|
||||
// Execute workflows for this trigger
|
||||
await executor.executeTriggeredWorkflows(
|
||||
change.collection.slug,
|
||||
operation,
|
||||
change.doc,
|
||||
change.previousDoc,
|
||||
change.req
|
||||
)
|
||||
|
||||
console.log('🚨 executeTriggeredWorkflows completed without error')
|
||||
|
||||
logger.info({
|
||||
slug: change.collection.slug,
|
||||
operation,
|
||||
docId: change.doc?.id
|
||||
}, 'AUTOMATION PLUGIN: executeTriggeredWorkflows completed successfully')
|
||||
} catch (error) {
|
||||
console.log('🚨 AUTOMATION PLUGIN ERROR:', error)
|
||||
|
||||
logger.error({
|
||||
slug: change.collection.slug,
|
||||
operation,
|
||||
docId: change.doc?.id,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined
|
||||
}, 'AUTOMATION PLUGIN: executeTriggeredWorkflows failed')
|
||||
// Don't re-throw to avoid breaking other hooks
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
115
test-jsonpath-condition.js
Normal file
115
test-jsonpath-condition.js
Normal file
@@ -0,0 +1,115 @@
|
||||
// Isolated JSONPath condition testing
|
||||
import { JSONPath } from 'jsonpath-plus'
|
||||
|
||||
function testJSONPathCondition() {
|
||||
console.log('🧪 Testing JSONPath condition evaluation in isolation')
|
||||
|
||||
// Simulate the exact context structure from workflow execution
|
||||
const testContext = {
|
||||
steps: {},
|
||||
trigger: {
|
||||
type: 'collection',
|
||||
collection: 'orders',
|
||||
doc: {
|
||||
id: '12345',
|
||||
orderName: 'Test Order',
|
||||
status: 'Paid', // This is the updated status
|
||||
customerEmail: 'test@example.com',
|
||||
totalPrice: 2500
|
||||
},
|
||||
operation: 'update',
|
||||
previousDoc: {
|
||||
id: '12345',
|
||||
orderName: 'Test Order',
|
||||
status: 'Unpaid', // This was the previous status
|
||||
customerEmail: 'test@example.com',
|
||||
totalPrice: 2500
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Test context:')
|
||||
console.log(' - trigger.doc.status:', testContext.trigger.doc.status)
|
||||
console.log(' - trigger.previousDoc.status:', testContext.trigger.previousDoc.status)
|
||||
|
||||
// Test different JSONPath expressions
|
||||
const testCases = [
|
||||
'$.trigger.doc.status',
|
||||
'$.doc.status', // This is what your condition uses but might be wrong!
|
||||
'$.trigger.doc.status == "Paid"',
|
||||
'$.trigger.doc.status == "Unpaid"'
|
||||
]
|
||||
|
||||
console.log('\n📋 Testing JSONPath expressions:')
|
||||
|
||||
for (const expression of testCases) {
|
||||
try {
|
||||
const result = JSONPath({
|
||||
json: testContext,
|
||||
path: expression,
|
||||
wrap: false
|
||||
})
|
||||
|
||||
console.log(` ✅ ${expression} => ${JSON.stringify(result)} (${typeof result})`)
|
||||
} catch (error) {
|
||||
console.log(` ❌ ${expression} => ERROR: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Test comparison logic manually
|
||||
console.log('\n🔍 Testing comparison logic:')
|
||||
|
||||
const condition = '$.doc.status == "Paid"' // Your original condition
|
||||
const correctCondition = '$.trigger.doc.status == "Paid"' // Likely correct path
|
||||
|
||||
console.log(`\nTesting: ${condition}`)
|
||||
try {
|
||||
const leftResult = JSONPath({
|
||||
json: testContext,
|
||||
path: '$.doc.status',
|
||||
wrap: false
|
||||
})
|
||||
console.log(` - Left side result: ${JSON.stringify(leftResult)}`)
|
||||
console.log(` - Is undefined/null? ${leftResult === undefined || leftResult === null}`)
|
||||
console.log(` - Comparison result: ${leftResult === 'Paid'}`)
|
||||
} catch (error) {
|
||||
console.log(` - Error: ${error.message}`)
|
||||
}
|
||||
|
||||
console.log(`\nTesting: ${correctCondition}`)
|
||||
try {
|
||||
const leftResult = JSONPath({
|
||||
json: testContext,
|
||||
path: '$.trigger.doc.status',
|
||||
wrap: false
|
||||
})
|
||||
console.log(` - Left side result: ${JSON.stringify(leftResult)}`)
|
||||
console.log(` - Comparison result: ${leftResult === 'Paid'}`)
|
||||
} catch (error) {
|
||||
console.log(` - Error: ${error.message}`)
|
||||
}
|
||||
|
||||
// Test regex parsing
|
||||
console.log('\n📝 Testing regex parsing:')
|
||||
const testConditions = [
|
||||
'$.trigger.doc.status == "Paid"',
|
||||
'$.doc.status == "Paid"',
|
||||
'$.trigger.doc.status=="Paid"', // No spaces
|
||||
"$.trigger.doc.status == 'Paid'" // Single quotes
|
||||
]
|
||||
|
||||
for (const cond of testConditions) {
|
||||
const comparisonMatch = cond.match(/^(.+?)\s*(==|!=|>|<|>=|<=)\s*(.+)$/)
|
||||
if (comparisonMatch) {
|
||||
const [, leftExpr, operator, rightExpr] = comparisonMatch
|
||||
console.log(` ✅ ${cond}`)
|
||||
console.log(` - Left: "${leftExpr.trim()}"`)
|
||||
console.log(` - Operator: "${operator}"`)
|
||||
console.log(` - Right: "${rightExpr.trim()}"`)
|
||||
} else {
|
||||
console.log(` ❌ ${cond} - No regex match`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testJSONPathCondition()
|
||||
44
test-published-workflows.js
Normal file
44
test-published-workflows.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Test script to verify published workflow filtering
|
||||
console.log('🔍 Testing published workflow filtering...')
|
||||
|
||||
// This will be run from the dev environment
|
||||
// Start the dev server first: pnpm dev
|
||||
// Then in another terminal: node test-published-workflows.js
|
||||
|
||||
const testData = {
|
||||
// Simulate what the workflow executor should find
|
||||
allWorkflows: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Draft Workflow',
|
||||
_status: 'draft',
|
||||
triggers: [{ type: 'collection-trigger', collectionSlug: 'orders', operation: 'update' }]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Published Workflow',
|
||||
_status: 'published',
|
||||
triggers: [{ type: 'collection-trigger', collectionSlug: 'orders', operation: 'update' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Test filtering logic
|
||||
const publishedOnly = testData.allWorkflows.filter(wf => wf._status === 'published')
|
||||
|
||||
console.log('All workflows:', testData.allWorkflows.length)
|
||||
console.log('Published workflows:', publishedOnly.length)
|
||||
console.log('Published workflow names:', publishedOnly.map(wf => wf.name))
|
||||
|
||||
console.log('\n✅ The published status filter should work!')
|
||||
console.log('💡 Make sure your workflow has _status: "published" in the database')
|
||||
|
||||
// Instructions for manual verification
|
||||
console.log('\n📋 Manual verification steps:')
|
||||
console.log('1. Start dev server: pnpm dev')
|
||||
console.log('2. Go to http://localhost:3000/admin/collections/workflows')
|
||||
console.log('3. Find your workflow and ensure it shows as "Published" (not "Draft")')
|
||||
console.log('4. If it shows as "Draft", click it and click "Publish"')
|
||||
console.log('5. Then test your order status change again')
|
||||
|
||||
process.exit(0)
|
||||
113
test-workflow-creation.js
Normal file
113
test-workflow-creation.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// Test script to create workflow with correct v0.0.15 schema structure
|
||||
const { getPayload } = require('payload')
|
||||
|
||||
async function testWorkflowCreation() {
|
||||
const payload = await getPayload({
|
||||
config: require('./dev/payload.config.ts').default
|
||||
})
|
||||
|
||||
console.log('🚀 Creating workflow with v0.0.15 schema...')
|
||||
|
||||
try {
|
||||
const workflow = await payload.create({
|
||||
collection: 'workflows',
|
||||
data: {
|
||||
name: 'Test Order Status Workflow v0.0.15',
|
||||
description: 'Test workflow that triggers when order status changes to Paid',
|
||||
enabled: true,
|
||||
triggers: [
|
||||
{
|
||||
type: 'collection-trigger',
|
||||
collectionSlug: 'orders',
|
||||
operation: 'update',
|
||||
// v0.0.15 uses 'condition' (singular) with JSONPath expressions
|
||||
// instead of 'conditions' array
|
||||
condition: '$.doc.status == "Paid"'
|
||||
}
|
||||
],
|
||||
steps: [
|
||||
{
|
||||
// v0.0.15 uses 'step' field instead of 'type'
|
||||
step: 'uppercaseText',
|
||||
name: 'Test Uppercase Step',
|
||||
// v0.0.15 uses 'input' (singular) instead of 'inputs'
|
||||
input: {
|
||||
inputText: 'Order {{$.trigger.doc.orderName}} has been paid!'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('✅ Workflow created successfully!')
|
||||
console.log('📋 Workflow details:')
|
||||
console.log(' - ID:', workflow.id)
|
||||
console.log(' - Name:', workflow.name)
|
||||
console.log(' - Triggers:', JSON.stringify(workflow.triggers, null, 2))
|
||||
console.log(' - Steps:', JSON.stringify(workflow.steps, null, 2))
|
||||
|
||||
// Now test with an order update
|
||||
console.log('\n🔄 Testing order status change...')
|
||||
|
||||
// First create a test order
|
||||
const order = await payload.create({
|
||||
collection: 'orders',
|
||||
data: {
|
||||
orderName: 'Test Order - ' + Date.now(),
|
||||
status: 'Unpaid',
|
||||
customerEmail: 'test@example.com',
|
||||
totalPrice: 2500,
|
||||
items: [
|
||||
{
|
||||
name: 'Test Item',
|
||||
quantity: 1,
|
||||
price: 2500
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
console.log('📦 Test order created:', order.id)
|
||||
|
||||
// Update order status to trigger workflow
|
||||
const updatedOrder = await payload.update({
|
||||
collection: 'orders',
|
||||
id: order.id,
|
||||
data: {
|
||||
status: 'Paid'
|
||||
}
|
||||
})
|
||||
|
||||
console.log('💰 Order status updated to:', updatedOrder.status)
|
||||
|
||||
// Wait a moment for async workflow execution
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// Check for workflow runs
|
||||
const workflowRuns = await payload.find({
|
||||
collection: 'workflow-runs',
|
||||
where: {
|
||||
workflow: {
|
||||
equals: workflow.id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`\n📊 Workflow runs found: ${workflowRuns.docs.length}`)
|
||||
|
||||
if (workflowRuns.docs.length > 0) {
|
||||
const run = workflowRuns.docs[0]
|
||||
console.log(' - Run ID:', run.id)
|
||||
console.log(' - Status:', run.status)
|
||||
console.log(' - Context:', JSON.stringify(run.context, null, 2))
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message)
|
||||
console.error('Stack:', error.stack)
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
testWorkflowCreation()
|
||||
Reference in New Issue
Block a user