fixes automation

This commit is contained in:
ValueOn AG 2025-11-03 09:58:15 +01:00
parent 22974653a2
commit 310e6d3f8b
3 changed files with 163 additions and 56 deletions

View file

@ -612,11 +612,24 @@ class DatabaseConnector:
# Add metadata # Add metadata
currentTime = getUtcTimestamp() currentTime = getUtcTimestamp()
# Set _createdAt and _createdBy if this is a new record (record doesn't have _createdAt)
if "_createdAt" not in record: if "_createdAt" not in record:
record["_createdAt"] = currentTime record["_createdAt"] = currentTime
record["_createdBy"] = self.userId # Only set _createdBy if userId is valid (not None or empty string)
if self.userId:
record["_createdBy"] = self.userId
else:
logger.warning(f"Attempting to create record with empty userId - _createdBy will not be set")
# Also ensure _createdBy is set even if _createdAt exists but _createdBy is missing/empty
elif "_createdBy" not in record or not record.get("_createdBy"):
if self.userId:
record["_createdBy"] = self.userId
else:
logger.warning(f"Attempting to set _createdBy with empty userId for record {recordId}")
# Always update modification metadata
record["_modifiedAt"] = currentTime record["_modifiedAt"] = currentTime
record["_modifiedBy"] = self.userId if self.userId:
record["_modifiedBy"] = self.userId
with self.connection.cursor() as cursor: with self.connection.cursor() as cursor:
self._save_record(cursor, table, recordId, record, model_class) self._save_record(cursor, table, recordId, record, model_class)

View file

@ -1071,6 +1071,13 @@ class AutomationDefinition(BaseModel):
frontend_readonly=True, frontend_readonly=True,
frontend_required=False frontend_required=False
) )
executionLogs: List[Dict[str, Any]] = Field(
default_factory=list,
description="List of execution logs, each containing timestamp, workflowId, status, and messages",
frontend_type="text",
frontend_readonly=True,
frontend_required=False
)
registerModelLabels( registerModelLabels(
@ -1086,5 +1093,6 @@ registerModelLabels(
"active": {"en": "Active", "fr": "Actif"}, "active": {"en": "Active", "fr": "Actif"},
"eventId": {"en": "Event ID", "fr": "ID de l'événement"}, "eventId": {"en": "Event ID", "fr": "ID de l'événement"},
"status": {"en": "Status", "fr": "Statut"}, "status": {"en": "Status", "fr": "Statut"},
"executionLogs": {"en": "Execution Logs", "fr": "Journaux d'exécution"},
}, },
) )

View file

@ -107,7 +107,11 @@ class ChatObjects:
objectFields[fieldName] = value objectFields[fieldName] = value
else: else:
# Field not in model - treat as scalar if simple, otherwise filter out # Field not in model - treat as scalar if simple, otherwise filter out
if isinstance(value, (str, int, float, bool, type(None))): # BUT: always include metadata fields (_createdBy, _createdAt, etc.) as they're handled by connector
if fieldName.startswith("_"):
# Metadata fields should be passed through to connector
simpleFields[fieldName] = value
elif isinstance(value, (str, int, float, bool, type(None))):
simpleFields[fieldName] = value simpleFields[fieldName] = value
else: else:
objectFields[fieldName] = value objectFields[fieldName] = value
@ -1284,6 +1288,16 @@ class ChatObjects:
if "mandateId" not in automationData: if "mandateId" not in automationData:
automationData["mandateId"] = self.mandateId automationData["mandateId"] = self.mandateId
# Ensure database connector has correct userId context
# The connector should have been initialized with userId, but ensure it's updated
if self.userId and hasattr(self.db, 'updateContext'):
try:
self.db.updateContext(self.userId)
except Exception as e:
logger.warning(f"Could not update database context: {e}")
# Note: _createdBy will be set automatically by connector's _saveRecord method
# when _createdAt is not present. We don't need to set it manually here.
# Use generic field separation # Use generic field separation
simpleFields, objectFields = self._separateObjectFields(AutomationDefinition, automationData) simpleFields, objectFields = self._separateObjectFields(AutomationDefinition, automationData)
@ -1383,60 +1397,132 @@ class ChatObjects:
async def executeAutomation(self, automationId: str) -> ChatWorkflow: async def executeAutomation(self, automationId: str) -> ChatWorkflow:
"""Execute automation workflow immediately (test mode) with placeholder replacement""" """Execute automation workflow immediately (test mode) with placeholder replacement"""
# 1. Load automation definition executionStartTime = getUtcTimestamp()
automation = self.getAutomationDefinition(automationId) executionLog = {
if not automation: "timestamp": executionStartTime,
raise ValueError(f"Automation {automationId} not found") "workflowId": None,
"status": "running",
"messages": []
}
# 2. Replace placeholders in template to generate plan try:
template = automation.get("template", "") # 1. Load automation definition
placeholders = automation.get("placeholders", {}) automation = self.getAutomationDefinition(automationId)
planJson = self._replacePlaceholders(template, placeholders) if not automation:
plan = json.loads(planJson) raise ValueError(f"Automation {automationId} not found")
# 3. Get user who created automation executionLog["messages"].append(f"Started execution at {executionStartTime}")
creator_user_id = automation.get("_createdBy")
if not creator_user_id:
raise ValueError(f"Automation {automationId} has no creator user")
# Get user from database # 2. Replace placeholders in template to generate plan
from modules.interfaces.interfaceDbAppObjects import getInterface as getAppInterface template = automation.get("template", "")
appInterface = getAppInterface(self.currentUser) placeholders = automation.get("placeholders", {})
creator_user = appInterface.getUser(creator_user_id) planJson = self._replacePlaceholders(template, placeholders)
if not creator_user: plan = json.loads(planJson)
raise ValueError(f"Creator user {creator_user_id} not found") executionLog["messages"].append("Template placeholders replaced successfully")
# 4. Create UserInputRequest from plan # 3. Get user who created automation
# Embed plan JSON in prompt for TemplateMode to extract creator_user_id = automation.get("_createdBy")
promptText = self._planToPrompt(plan)
planJson = json.dumps(plan)
# Embed plan as JSON comment so TemplateMode can extract it
promptWithPlan = f"{promptText}\n\n<!--TEMPLATE_PLAN_START-->\n{planJson}\n<!--TEMPLATE_PLAN_END-->"
userInput = UserInputRequest( # If _createdBy is missing, try to fix it by setting it to current user
prompt=promptWithPlan, # This handles automations created before _createdBy was required
listFileId=[], if not creator_user_id:
userLanguage=creator_user.language or "en" logger.warning(f"Automation {automationId} has no creator user, setting to current user {self.userId}")
) try:
# Update the automation to set _createdBy
self.db.recordModify(
AutomationDefinition,
automationId,
{"_createdBy": self.userId}
)
creator_user_id = self.userId
automation["_createdBy"] = self.userId
logger.info(f"Fixed automation {automationId} by setting _createdBy to {self.userId}")
executionLog["messages"].append(f"Fixed missing _createdBy field, set to user {self.userId}")
except Exception as e:
logger.error(f"Error fixing automation {automationId}: {str(e)}")
raise ValueError(f"Automation {automationId} has no creator user and could not be fixed")
# 5. Start workflow using chatStart # Get user from database
from modules.features.chatPlayground.mainChatPlayground import chatStart from modules.interfaces.interfaceDbAppObjects import getInterface as getAppInterface
appInterface = getAppInterface(self.currentUser)
creator_user = appInterface.getUser(creator_user_id)
if not creator_user:
raise ValueError(f"Creator user {creator_user_id} not found")
workflow = await chatStart( executionLog["messages"].append(f"Using creator user: {creator_user_id}")
currentUser=creator_user,
userInput=userInput,
workflowMode=WorkflowModeEnum.WORKFLOW_AUTOMATION,
workflowId=None
)
# Also store plan in module-level cache as backup (keyed by workflow ID) # 4. Create UserInputRequest from plan
from modules.workflows.processing.modes import modeAutomation # Embed plan JSON in prompt for TemplateMode to extract
if not hasattr(modeAutomation, '_templatePlanCache'): promptText = self._planToPrompt(plan)
modeAutomation._templatePlanCache = {} planJson = json.dumps(plan)
modeAutomation._templatePlanCache[workflow.id] = plan # Embed plan as JSON comment so TemplateMode can extract it
logger.info(f"Stored template plan for workflow {workflow.id} (cache + prompt) with {len(plan.get('tasks', []))} tasks") promptWithPlan = f"{promptText}\n\n<!--TEMPLATE_PLAN_START-->\n{planJson}\n<!--TEMPLATE_PLAN_END-->"
return workflow userInput = UserInputRequest(
prompt=promptWithPlan,
listFileId=[],
userLanguage=creator_user.language or "en"
)
executionLog["messages"].append("Starting workflow execution")
# 5. Start workflow using chatStart
from modules.features.chatPlayground.mainChatPlayground import chatStart
workflow = await chatStart(
currentUser=creator_user,
userInput=userInput,
workflowMode=WorkflowModeEnum.WORKFLOW_AUTOMATION,
workflowId=None
)
executionLog["workflowId"] = workflow.id
executionLog["status"] = "completed"
executionLog["messages"].append(f"Workflow {workflow.id} started successfully")
# Also store plan in module-level cache as backup (keyed by workflow ID)
from modules.workflows.processing.modes import modeAutomation
if not hasattr(modeAutomation, '_templatePlanCache'):
modeAutomation._templatePlanCache = {}
modeAutomation._templatePlanCache[workflow.id] = plan
logger.info(f"Stored template plan for workflow {workflow.id} (cache + prompt) with {len(plan.get('tasks', []))} tasks")
# Update automation with execution log
executionLogs = automation.get("executionLogs", [])
executionLogs.append(executionLog)
# Keep only last 50 executions
if len(executionLogs) > 50:
executionLogs = executionLogs[-50:]
self.db.recordModify(
AutomationDefinition,
automationId,
{"executionLogs": executionLogs}
)
return workflow
except Exception as e:
# Log error to execution log
executionLog["status"] = "error"
executionLog["messages"].append(f"Error: {str(e)}")
# Update automation with execution log even on error
try:
automation = self.getAutomationDefinition(automationId)
if automation:
executionLogs = automation.get("executionLogs", [])
executionLogs.append(executionLog)
if len(executionLogs) > 50:
executionLogs = executionLogs[-50:]
self.db.recordModify(
AutomationDefinition,
automationId,
{"executionLogs": executionLogs}
)
except Exception as logError:
logger.error(f"Error saving execution log: {str(logError)}")
raise
def _planToPrompt(self, plan: Dict) -> str: def _planToPrompt(self, plan: Dict) -> str:
"""Convert plan structure to prompt string for workflow execution""" """Convert plan structure to prompt string for workflow execution"""