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
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue