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
currentTime = getUtcTimestamp()
# Set _createdAt and _createdBy if this is a new record (record doesn't have _createdAt)
if "_createdAt" not in record:
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["_modifiedBy"] = self.userId
if self.userId:
record["_modifiedBy"] = self.userId
with self.connection.cursor() as cursor:
self._save_record(cursor, table, recordId, record, model_class)

View file

@ -1071,6 +1071,13 @@ class AutomationDefinition(BaseModel):
frontend_readonly=True,
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(
@ -1086,5 +1093,6 @@ registerModelLabels(
"active": {"en": "Active", "fr": "Actif"},
"eventId": {"en": "Event ID", "fr": "ID de l'événement"},
"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
else:
# 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
else:
objectFields[fieldName] = value
@ -1284,6 +1288,16 @@ class ChatObjects:
if "mandateId" not in automationData:
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
simpleFields, objectFields = self._separateObjectFields(AutomationDefinition, automationData)
@ -1383,60 +1397,132 @@ class ChatObjects:
async def executeAutomation(self, automationId: str) -> ChatWorkflow:
"""Execute automation workflow immediately (test mode) with placeholder replacement"""
# 1. Load automation definition
automation = self.getAutomationDefinition(automationId)
if not automation:
raise ValueError(f"Automation {automationId} not found")
executionStartTime = getUtcTimestamp()
executionLog = {
"timestamp": executionStartTime,
"workflowId": None,
"status": "running",
"messages": []
}
# 2. Replace placeholders in template to generate plan
template = automation.get("template", "")
placeholders = automation.get("placeholders", {})
planJson = self._replacePlaceholders(template, placeholders)
plan = json.loads(planJson)
# 3. Get user who created automation
creator_user_id = automation.get("_createdBy")
if not creator_user_id:
raise ValueError(f"Automation {automationId} has no creator user")
# Get user from database
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")
# 4. Create UserInputRequest from plan
# Embed plan JSON in prompt for TemplateMode to extract
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(
prompt=promptWithPlan,
listFileId=[],
userLanguage=creator_user.language or "en"
)
# 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
)
# 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")
return workflow
try:
# 1. Load automation definition
automation = self.getAutomationDefinition(automationId)
if not automation:
raise ValueError(f"Automation {automationId} not found")
executionLog["messages"].append(f"Started execution at {executionStartTime}")
# 2. Replace placeholders in template to generate plan
template = automation.get("template", "")
placeholders = automation.get("placeholders", {})
planJson = self._replacePlaceholders(template, placeholders)
plan = json.loads(planJson)
executionLog["messages"].append("Template placeholders replaced successfully")
# 3. Get user who created automation
creator_user_id = automation.get("_createdBy")
# If _createdBy is missing, try to fix it by setting it to current user
# This handles automations created before _createdBy was required
if not creator_user_id:
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")
# Get user from database
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")
executionLog["messages"].append(f"Using creator user: {creator_user_id}")
# 4. Create UserInputRequest from plan
# Embed plan JSON in prompt for TemplateMode to extract
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(
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:
"""Convert plan structure to prompt string for workflow execution"""