feat: implemented the service center into the automation feature
This commit is contained in:
parent
9d1a8d53fc
commit
853cce2cf0
5 changed files with 126 additions and 20 deletions
|
|
@ -6,7 +6,7 @@ Handles feature initialization and RBAC catalog registration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any, Optional
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -121,6 +121,106 @@ TEMPLATE_ROLES = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Service requirements - services this feature needs from the service center
|
||||||
|
REQUIRED_SERVICES = [
|
||||||
|
{"serviceKey": "chat", "meta": {"usage": "Workflow CRUD, messages, logs"}},
|
||||||
|
{"serviceKey": "ai", "meta": {"usage": "AI planning for workflow execution"}},
|
||||||
|
{"serviceKey": "utils", "meta": {"usage": "Timestamps, utilities"}},
|
||||||
|
{"serviceKey": "billing", "meta": {"usage": "AI call billing"}},
|
||||||
|
{"serviceKey": "extraction", "meta": {"usage": "Workflow method actions"}},
|
||||||
|
{"serviceKey": "sharepoint", "meta": {"usage": "SharePoint actions (listDocuments, uploadDocument, etc.)"}},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def getRequiredServiceKeys() -> List[str]:
|
||||||
|
"""Return list of service keys this feature requires."""
|
||||||
|
return [s["serviceKey"] for s in REQUIRED_SERVICES]
|
||||||
|
|
||||||
|
|
||||||
|
def getAutomationServices(
|
||||||
|
user,
|
||||||
|
mandateId: Optional[str] = None,
|
||||||
|
featureInstanceId: Optional[str] = None,
|
||||||
|
workflow=None,
|
||||||
|
) -> "_AutomationServiceHub":
|
||||||
|
"""
|
||||||
|
Get a service hub for the automation feature using the service center.
|
||||||
|
Resolves only the services declared in REQUIRED_SERVICES.
|
||||||
|
No legacy fallback - service center only.
|
||||||
|
|
||||||
|
Returns a hub-like object with: chat, ai, utils, billing, extraction,
|
||||||
|
sharepoint, rbac, interfaceDbApp, interfaceDbComponent, interfaceDbChat,
|
||||||
|
interfaceDbAutomation.
|
||||||
|
"""
|
||||||
|
from modules.serviceCenter import getService
|
||||||
|
from modules.serviceCenter.context import ServiceCenterContext
|
||||||
|
from modules.features.automation.interfaceFeatureAutomation import getInterface as getAutomationInterface
|
||||||
|
|
||||||
|
_workflow = workflow
|
||||||
|
if _workflow is None:
|
||||||
|
_workflow = type("_Placeholder", (), {"featureCode": FEATURE_CODE})()
|
||||||
|
ctx = ServiceCenterContext(
|
||||||
|
user=user,
|
||||||
|
mandate_id=mandateId,
|
||||||
|
feature_instance_id=featureInstanceId,
|
||||||
|
workflow=_workflow,
|
||||||
|
)
|
||||||
|
|
||||||
|
hub = _AutomationServiceHub()
|
||||||
|
hub.user = user
|
||||||
|
hub.mandateId = mandateId
|
||||||
|
hub.featureInstanceId = featureInstanceId
|
||||||
|
hub.workflow = workflow
|
||||||
|
hub.featureCode = FEATURE_CODE
|
||||||
|
hub.allowedProviders = None
|
||||||
|
|
||||||
|
for spec in REQUIRED_SERVICES:
|
||||||
|
key = spec["serviceKey"]
|
||||||
|
try:
|
||||||
|
svc = getService(key, ctx, legacy_hub=None)
|
||||||
|
setattr(hub, key, svc)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not resolve service '{key}' for automation: {e}")
|
||||||
|
setattr(hub, key, None)
|
||||||
|
|
||||||
|
# Copy interfaces from chat service for WorkflowManager compatibility
|
||||||
|
if hub.chat:
|
||||||
|
hub.interfaceDbApp = getattr(hub.chat, "interfaceDbApp", None)
|
||||||
|
hub.interfaceDbComponent = getattr(hub.chat, "interfaceDbComponent", None)
|
||||||
|
hub.interfaceDbChat = getattr(hub.chat, "interfaceDbChat", None)
|
||||||
|
|
||||||
|
# RBAC for MethodBase action permission checks (workflow methods)
|
||||||
|
hub.rbac = getattr(hub.interfaceDbApp, "rbac", None) if hub.interfaceDbApp else None
|
||||||
|
|
||||||
|
# Set interfaceDbAutomation from feature interface
|
||||||
|
hub.interfaceDbAutomation = getAutomationInterface(
|
||||||
|
user, mandateId=mandateId, featureInstanceId=featureInstanceId
|
||||||
|
)
|
||||||
|
|
||||||
|
return hub
|
||||||
|
|
||||||
|
|
||||||
|
class _AutomationServiceHub:
|
||||||
|
"""Lightweight hub exposing only services required by the automation feature."""
|
||||||
|
|
||||||
|
user = None
|
||||||
|
mandateId = None
|
||||||
|
featureInstanceId = None
|
||||||
|
workflow = None
|
||||||
|
featureCode = "automation"
|
||||||
|
allowedProviders = None
|
||||||
|
interfaceDbApp = None
|
||||||
|
interfaceDbComponent = None
|
||||||
|
interfaceDbChat = None
|
||||||
|
interfaceDbAutomation = None
|
||||||
|
rbac = None
|
||||||
|
chat = None
|
||||||
|
ai = None
|
||||||
|
utils = None
|
||||||
|
billing = None
|
||||||
|
extraction = None
|
||||||
|
sharepoint = None
|
||||||
|
|
||||||
|
|
||||||
def getFeatureDefinition() -> Dict[str, Any]:
|
def getFeatureDefinition() -> Dict[str, Any]:
|
||||||
"""Return the feature definition for registration."""
|
"""Return the feature definition for registration."""
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import json
|
||||||
|
|
||||||
# Import interfaces and models
|
# Import interfaces and models
|
||||||
from modules.features.automation.interfaceFeatureAutomation import getInterface as getAutomationInterface
|
from modules.features.automation.interfaceFeatureAutomation import getInterface as getAutomationInterface
|
||||||
|
from modules.features.automation.mainAutomation import getAutomationServices
|
||||||
from modules.auth import limiter, getRequestContext, RequestContext
|
from modules.auth import limiter, getRequestContext, RequestContext
|
||||||
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition, AutomationTemplate
|
from modules.features.automation.datamodelFeatureAutomation import AutomationDefinition, AutomationTemplate
|
||||||
from modules.datamodels.datamodelChat import ChatWorkflow
|
from modules.datamodels.datamodelChat import ChatWorkflow
|
||||||
|
|
@ -147,12 +148,14 @@ def get_available_actions(
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from modules.workflows.processing.shared.methodDiscovery import methods, discoverMethods
|
from modules.workflows.processing.shared.methodDiscovery import methods, discoverMethods
|
||||||
from modules.services import getInterface as getServices
|
|
||||||
|
|
||||||
# Ensure methods are discovered (need a service center for discovery)
|
# Ensure methods are discovered (need a service hub for discovery)
|
||||||
if not methods:
|
if not methods:
|
||||||
# Create a lightweight service center for method discovery
|
services = getAutomationServices(
|
||||||
services = getServices(context.user, mandateId=context.mandateId)
|
context.user,
|
||||||
|
mandateId=context.mandateId,
|
||||||
|
featureInstanceId=context.featureInstanceId,
|
||||||
|
)
|
||||||
discoverMethods(services)
|
discoverMethods(services)
|
||||||
|
|
||||||
actionsList = []
|
actionsList = []
|
||||||
|
|
@ -365,8 +368,11 @@ async def execute_automation_route(
|
||||||
) -> ChatWorkflow:
|
) -> ChatWorkflow:
|
||||||
"""Execute an automation immediately (test mode)"""
|
"""Execute an automation immediately (test mode)"""
|
||||||
try:
|
try:
|
||||||
from modules.services import getInterface as getServices
|
services = getAutomationServices(
|
||||||
services = getServices(context.user, mandateId=context.mandateId, featureInstanceId=context.featureInstanceId)
|
context.user,
|
||||||
|
mandateId=context.mandateId,
|
||||||
|
featureInstanceId=context.featureInstanceId,
|
||||||
|
)
|
||||||
|
|
||||||
# Load automation with current user's context (user has RBAC permissions via UI)
|
# Load automation with current user's context (user has RBAC permissions via UI)
|
||||||
automation = services.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)
|
automation = services.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ def get_all_automation_events(
|
||||||
try:
|
try:
|
||||||
from modules.shared.eventManagement import eventManager
|
from modules.shared.eventManagement import eventManager
|
||||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||||
from modules.services import getInterface as getServices
|
from modules.features.automation.mainAutomation import getAutomationServices
|
||||||
|
|
||||||
if not eventManager.scheduler:
|
if not eventManager.scheduler:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -74,7 +74,7 @@ def get_all_automation_events(
|
||||||
rootInterface = getRootInterface()
|
rootInterface = getRootInterface()
|
||||||
eventUser = rootInterface.getUserByUsername("event")
|
eventUser = rootInterface.getUserByUsername("event")
|
||||||
if eventUser:
|
if eventUser:
|
||||||
services = getServices(currentUser, None)
|
services = getAutomationServices(currentUser, mandateId=None, featureInstanceId=None)
|
||||||
allAutomations = services.interfaceDbAutomation.getAllAutomationDefinitionsWithRBAC(eventUser)
|
allAutomations = services.interfaceDbAutomation.getAllAutomationDefinitionsWithRBAC(eventUser)
|
||||||
|
|
||||||
# Build lookup by automation ID
|
# Build lookup by automation ID
|
||||||
|
|
@ -171,8 +171,8 @@ async def sync_all_automation_events(
|
||||||
detail="Event user not available"
|
detail="Event user not available"
|
||||||
)
|
)
|
||||||
|
|
||||||
from modules.services import getInterface as getServices
|
from modules.features.automation.mainAutomation import getAutomationServices
|
||||||
services = getServices(currentUser, None)
|
services = getAutomationServices(currentUser, mandateId=None, featureInstanceId=None)
|
||||||
result = syncAutomationEvents(services, eventUser)
|
result = syncAutomationEvents(services, eventUser)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from modules.features.automation.datamodelFeatureAutomation import AutomationDef
|
||||||
from modules.datamodels.datamodelUam import User
|
from modules.datamodels.datamodelUam import User
|
||||||
from modules.shared.timeUtils import getUtcTimestamp
|
from modules.shared.timeUtils import getUtcTimestamp
|
||||||
from modules.shared.eventManagement import eventManager
|
from modules.shared.eventManagement import eventManager
|
||||||
from modules.services import getInterface as getServices
|
from modules.features.automation.mainAutomation import getAutomationServices
|
||||||
from modules.workflows.workflowManager import WorkflowManager
|
from modules.workflows.workflowManager import WorkflowManager
|
||||||
from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceholders
|
from .subAutomationUtils import parseScheduleToCron, planToPrompt, replacePlaceholders
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
|
||||||
featureCode: Feature code (e.g., 'chatplayground', 'automation')
|
featureCode: Feature code (e.g., 'chatplayground', 'automation')
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
services = getServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
||||||
|
|
||||||
# Store allowedProviders in services context for model selection
|
# Store allowedProviders in services context for model selection
|
||||||
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
|
if hasattr(userInput, 'allowedProviders') and userInput.allowedProviders:
|
||||||
|
|
@ -59,7 +59,7 @@ async def chatStart(currentUser: User, userInput: UserInputRequest, workflowMode
|
||||||
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
|
async def chatStop(currentUser: User, workflowId: str, mandateId: Optional[str] = None, featureInstanceId: Optional[str] = None, featureCode: Optional[str] = None) -> ChatWorkflow:
|
||||||
"""Stops a running chat."""
|
"""Stops a running chat."""
|
||||||
try:
|
try:
|
||||||
services = getServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
services = getAutomationServices(currentUser, mandateId=mandateId, featureInstanceId=featureInstanceId)
|
||||||
if featureCode:
|
if featureCode:
|
||||||
services.featureCode = featureCode
|
services.featureCode = featureCode
|
||||||
workflowManager = WorkflowManager(services)
|
workflowManager = WorkflowManager(services)
|
||||||
|
|
@ -163,7 +163,7 @@ async def executeAutomation(automationId: str, automation, creatorUser: User, se
|
||||||
|
|
||||||
# Set workflow name with "automated" prefix — use creatorUser's Services
|
# Set workflow name with "automated" prefix — use creatorUser's Services
|
||||||
# (services parameter is eventServices with eventUser context, must use creatorUser context)
|
# (services parameter is eventServices with eventUser context, must use creatorUser context)
|
||||||
creatorServices = getServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
|
creatorServices = getAutomationServices(creatorUser, mandateId=automationMandateId, featureInstanceId=automationFeatureInstanceId)
|
||||||
automationLabel = automation.label or "Unknown Automation"
|
automationLabel = automation.label or "Unknown Automation"
|
||||||
workflowName = f"automated: {automationLabel}"
|
workflowName = f"automated: {automationLabel}"
|
||||||
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})
|
creatorServices.interfaceDbChat.updateWorkflow(workflow.id, {"name": workflowName})
|
||||||
|
|
@ -292,7 +292,7 @@ def createAutomationEventHandler(automationId: str, eventUser):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Load automation using SysAdmin eventUser (has unrestricted access)
|
# Load automation using SysAdmin eventUser (has unrestricted access)
|
||||||
eventServices = getServices(eventUser, None)
|
eventServices = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
|
||||||
automation = eventServices.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)
|
automation = eventServices.interfaceDbAutomation.getAutomationDefinition(automationId, includeSystemFields=True)
|
||||||
if not automation or not getattr(automation, "active", False):
|
if not automation or not getattr(automation, "active", False):
|
||||||
logger.warning(f"Automation {automationId} not found or not active, skipping execution")
|
logger.warning(f"Automation {automationId} not found or not active, skipping execution")
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ the automation scheduler (loading/syncing scheduled automation events).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from modules.services import getInterface as getServices
|
from modules.features.automation.mainAutomation import getAutomationServices
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -31,12 +31,12 @@ def start(eventUser) -> bool:
|
||||||
from modules.shared.callbackRegistry import callbackRegistry
|
from modules.shared.callbackRegistry import callbackRegistry
|
||||||
|
|
||||||
# Get services for event user (provides access to interfaces)
|
# Get services for event user (provides access to interfaces)
|
||||||
services = getServices(eventUser, None)
|
services = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
|
||||||
|
|
||||||
# Register callback for automation changes
|
# Register callback for automation changes
|
||||||
def onAutomationChanged(chatInterface):
|
def onAutomationChanged(chatInterface):
|
||||||
"""Callback triggered when automations are created/updated/deleted."""
|
"""Callback triggered when automations are created/updated/deleted."""
|
||||||
eventServices = getServices(eventUser, None)
|
eventServices = getAutomationServices(eventUser, mandateId=None, featureInstanceId=None)
|
||||||
syncAutomationEvents(eventServices, eventUser)
|
syncAutomationEvents(eventServices, eventUser)
|
||||||
|
|
||||||
callbackRegistry.register('automation.changed', onAutomationChanged)
|
callbackRegistry.register('automation.changed', onAutomationChanged)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue