fixes automation
This commit is contained in:
parent
22974653a2
commit
310e6d3f8b
3 changed files with 163 additions and 56 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
# 2. Replace placeholders in template to generate plan
|
||||||
raise ValueError(f"Automation {automationId} has no creator user")
|
template = automation.get("template", "")
|
||||||
|
placeholders = automation.get("placeholders", {})
|
||||||
# Get user from database
|
planJson = self._replacePlaceholders(template, placeholders)
|
||||||
from modules.interfaces.interfaceDbAppObjects import getInterface as getAppInterface
|
plan = json.loads(planJson)
|
||||||
appInterface = getAppInterface(self.currentUser)
|
executionLog["messages"].append("Template placeholders replaced successfully")
|
||||||
creator_user = appInterface.getUser(creator_user_id)
|
|
||||||
if not creator_user:
|
# 3. Get user who created automation
|
||||||
raise ValueError(f"Creator user {creator_user_id} not found")
|
creator_user_id = automation.get("_createdBy")
|
||||||
|
|
||||||
# 4. Create UserInputRequest from plan
|
# If _createdBy is missing, try to fix it by setting it to current user
|
||||||
# Embed plan JSON in prompt for TemplateMode to extract
|
# This handles automations created before _createdBy was required
|
||||||
promptText = self._planToPrompt(plan)
|
if not creator_user_id:
|
||||||
planJson = json.dumps(plan)
|
logger.warning(f"Automation {automationId} has no creator user, setting to current user {self.userId}")
|
||||||
# Embed plan as JSON comment so TemplateMode can extract it
|
try:
|
||||||
promptWithPlan = f"{promptText}\n\n<!--TEMPLATE_PLAN_START-->\n{planJson}\n<!--TEMPLATE_PLAN_END-->"
|
# Update the automation to set _createdBy
|
||||||
|
self.db.recordModify(
|
||||||
userInput = UserInputRequest(
|
AutomationDefinition,
|
||||||
prompt=promptWithPlan,
|
automationId,
|
||||||
listFileId=[],
|
{"_createdBy": self.userId}
|
||||||
userLanguage=creator_user.language or "en"
|
)
|
||||||
)
|
creator_user_id = self.userId
|
||||||
|
automation["_createdBy"] = self.userId
|
||||||
# 5. Start workflow using chatStart
|
logger.info(f"Fixed automation {automationId} by setting _createdBy to {self.userId}")
|
||||||
from modules.features.chatPlayground.mainChatPlayground import chatStart
|
executionLog["messages"].append(f"Fixed missing _createdBy field, set to user {self.userId}")
|
||||||
|
except Exception as e:
|
||||||
workflow = await chatStart(
|
logger.error(f"Error fixing automation {automationId}: {str(e)}")
|
||||||
currentUser=creator_user,
|
raise ValueError(f"Automation {automationId} has no creator user and could not be fixed")
|
||||||
userInput=userInput,
|
|
||||||
workflowMode=WorkflowModeEnum.WORKFLOW_AUTOMATION,
|
# Get user from database
|
||||||
workflowId=None
|
from modules.interfaces.interfaceDbAppObjects import getInterface as getAppInterface
|
||||||
)
|
appInterface = getAppInterface(self.currentUser)
|
||||||
|
creator_user = appInterface.getUser(creator_user_id)
|
||||||
# Also store plan in module-level cache as backup (keyed by workflow ID)
|
if not creator_user:
|
||||||
from modules.workflows.processing.modes import modeAutomation
|
raise ValueError(f"Creator user {creator_user_id} not found")
|
||||||
if not hasattr(modeAutomation, '_templatePlanCache'):
|
|
||||||
modeAutomation._templatePlanCache = {}
|
executionLog["messages"].append(f"Using creator user: {creator_user_id}")
|
||||||
modeAutomation._templatePlanCache[workflow.id] = plan
|
|
||||||
logger.info(f"Stored template plan for workflow {workflow.id} (cache + prompt) with {len(plan.get('tasks', []))} tasks")
|
# 4. Create UserInputRequest from plan
|
||||||
|
# Embed plan JSON in prompt for TemplateMode to extract
|
||||||
return workflow
|
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:
|
def _planToPrompt(self, plan: Dict) -> str:
|
||||||
"""Convert plan structure to prompt string for workflow execution"""
|
"""Convert plan structure to prompt string for workflow execution"""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue