automation fixes
Some checks failed
Deploy Plattform-Core (Int) / test (push) Failing after 1m9s
Deploy Plattform-Core (Int) / deploy (push) Has been skipped

This commit is contained in:
ValueOn AG 2026-06-09 22:59:26 +02:00
parent ebc4b2a080
commit 06e68c343b
17 changed files with 207 additions and 85 deletions

2
app.py
View file

@ -501,6 +501,8 @@ async def lifespan(app: FastAPI):
return
if isinstance(exc, ConnectionAbortedError):
return
if exc and "LocalProtocolError" in type(exc).__name__:
return
loop.default_exception_handler(ctx)
main_loop.set_exception_handler(_suppressClientDisconnect)
except RuntimeError:

View file

@ -150,52 +150,21 @@ NAVIGATION_SECTIONS = [
},
],
},
# --- Workflow-Automation (System-Komponente, cross-mandate) ---
# --- Solution Design (System-Komponente, cross-mandate) ---
# Single nav entry; tabs are managed internally by WorkflowAutomationHubPage.
{
"id": "workflowAutomation",
"title": t("Workflow-Automation"),
"title": t("Lösungsdesign"),
"order": 25,
"items": [
{
"id": "wa-workflows",
"objectKey": "ui.system.workflowAutomation.workflows",
"label": t("Workflows"),
"id": "wa-hub",
"objectKey": "ui.system.workflowAutomation",
"label": t("Workflow-Automation"),
"icon": "FaSitemap",
"path": "/workflow-automation?tab=workflows",
"path": "/workflow-automation",
"order": 10,
},
{
"id": "wa-editor",
"objectKey": "ui.system.workflowAutomation.editor",
"label": t("Editor"),
"icon": "FaProjectDiagram",
"path": "/workflow-automation?tab=editor",
"order": 20,
},
{
"id": "wa-templates",
"objectKey": "ui.system.workflowAutomation.templates",
"label": t("Vorlagen"),
"icon": "FaCopy",
"path": "/workflow-automation?tab=templates",
"order": 30,
},
{
"id": "wa-runs",
"objectKey": "ui.system.workflowAutomation.runs",
"label": t("Läufe"),
"icon": "FaPlay",
"path": "/workflow-automation?tab=runs",
"order": 40,
},
{
"id": "wa-tasks",
"objectKey": "ui.system.workflowAutomation.tasks",
"label": t("Tasks"),
"icon": "FaTasks",
"path": "/workflow-automation?tab=tasks",
"order": 50,
},
],
},
# --- Administration (with subgroups) ---
@ -237,7 +206,7 @@ NAVIGATION_SECTIONS = [
{
"id": "admin-users",
"objectKey": "ui.admin.users",
"label": t("Benutzer"),
"label": t("Übersicht"),
"icon": "FaUsers",
"path": "/admin/users",
"order": 10,
@ -246,7 +215,7 @@ NAVIGATION_SECTIONS = [
{
"id": "admin-invitations",
"objectKey": "ui.admin.invitations",
"label": t("Benutzer-Einladungen"),
"label": t("Einladungen"),
"icon": "FaEnvelopeOpenText",
"path": "/admin/invitations",
"order": 20,
@ -255,7 +224,7 @@ NAVIGATION_SECTIONS = [
{
"id": "admin-user-access-overview",
"objectKey": "ui.admin.userAccessOverview",
"label": t("Benutzer-Zugriffsübersicht"),
"label": t("Zugriffe"),
"icon": "FaClipboardList",
"path": "/admin/user-access-overview",
"order": 30,

View file

@ -96,6 +96,23 @@ def resolveRoleLabels(db, ids: List[str]) -> Dict[str, Optional[str]]:
return out
def resolveFileLabels(db, ids: List[str]) -> Dict[str, Optional[str]]:
"""Resolve FileItem IDs to fileName. Returns None for unresolvable."""
if not ids:
return {}
from modules.datamodels.datamodelFiles import FileItem as _FileItem
recs = db.getRecordset(
_FileItem,
recordFilter={"id": list(set(ids))},
) or []
out: Dict[str, Optional[str]] = {i: None for i in ids}
for r in recs:
fid = r.get("id")
if fid:
out[fid] = r.get("fileName") or None
return out
# ---------------------------------------------------------------------------
# Resolver registry
# ---------------------------------------------------------------------------
@ -105,6 +122,7 @@ _BUILTIN_FK_RESOLVERS: Dict[str, Callable] = {
"FeatureInstance": resolveInstanceLabels,
"UserInDB": resolveUserLabels,
"Role": resolveRoleLabels,
"FileItem": resolveFileLabels,
}

View file

@ -263,7 +263,7 @@ def get_projects(
if mode == "filterValues":
if not column:
raise HTTPException(status_code=400, detail="column parameter required for mode=filterValues")
enrichRowsWithFkLabels(itemDicts, Projekt, db=interface.db)
enrichRowsWithFkLabels(itemDicts, Projekt, db=getRootInterface().db)
return handleFilterValuesInMemory(itemDicts, column, pagination)
return handleIdsInMemory(itemDicts, pagination)
@ -271,7 +271,9 @@ def get_projects(
paginationParams = _parsePagination(pagination)
if paginationParams:
from modules.dbHelpers.paginationHelpers import applyFiltersAndSort
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
itemDicts = [i.model_dump() if hasattr(i, 'model_dump') else i for i in items]
enrichRowsWithFkLabels(itemDicts, Projekt, db=getRootInterface().db)
filtered = applyFiltersAndSort(itemDicts, paginationParams)
total_items = len(filtered)
total_pages = (total_items + paginationParams.pageSize - 1) // paginationParams.pageSize
@ -289,7 +291,10 @@ def get_projects(
filters=paginationParams.filters
)
)
return PaginatedResponse(items=items, pagination=None)
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
itemDicts = [i.model_dump() if hasattr(i, 'model_dump') else i for i in items]
enrichRowsWithFkLabels(itemDicts, Projekt, db=getRootInterface().db)
return PaginatedResponse(items=itemDicts, pagination=None)
@router.get("/{instanceId}/projects/{projectId}", response_model=Projekt)
@ -405,7 +410,7 @@ def get_parcels(
if mode == "filterValues":
if not column:
raise HTTPException(status_code=400, detail="column parameter required for mode=filterValues")
enrichRowsWithFkLabels(itemDicts, Parzelle, db=interface.db)
enrichRowsWithFkLabels(itemDicts, Parzelle, db=getRootInterface().db)
return handleFilterValuesInMemory(itemDicts, column, pagination)
return handleIdsInMemory(itemDicts, pagination)
@ -413,7 +418,9 @@ def get_parcels(
paginationParams = _parsePagination(pagination)
if paginationParams:
from modules.dbHelpers.paginationHelpers import applyFiltersAndSort
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
itemDicts = [i.model_dump() if hasattr(i, 'model_dump') else i for i in items]
enrichRowsWithFkLabels(itemDicts, Parzelle, db=getRootInterface().db)
filtered = applyFiltersAndSort(itemDicts, paginationParams)
total_items = len(filtered)
total_pages = (total_items + paginationParams.pageSize - 1) // paginationParams.pageSize
@ -431,7 +438,10 @@ def get_parcels(
filters=paginationParams.filters
)
)
return PaginatedResponse(items=items, pagination=None)
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
itemDicts = [i.model_dump() if hasattr(i, 'model_dump') else i for i in items]
enrichRowsWithFkLabels(itemDicts, Parzelle, db=getRootInterface().db)
return PaginatedResponse(items=itemDicts, pagination=None)
@router.get("/{instanceId}/parcels/{parcelId}", response_model=Parzelle)

View file

@ -437,7 +437,7 @@ def get_organisations(
return [r.model_dump() if hasattr(r, "model_dump") else r for r in items]
if paginationParams and hasattr(result, 'items'):
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeOrganisation, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeOrganisation, db=getRootInterface().db)
return {
"items": enriched,
"pagination": PaginationMetadata(
@ -450,7 +450,7 @@ def get_organisations(
).model_dump(),
}
items = result if isinstance(result, list) else result.items
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeOrganisation, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeOrganisation, db=getRootInterface().db)
return {"items": enriched, "pagination": None}
@ -557,7 +557,7 @@ def get_roles(
return [r.model_dump() if hasattr(r, "model_dump") else r for r in items]
if paginationParams and hasattr(result, 'items'):
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeRole, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeRole, db=getRootInterface().db)
return {
"items": enriched,
"pagination": PaginationMetadata(
@ -570,7 +570,7 @@ def get_roles(
).model_dump(),
}
items = result if isinstance(result, list) else result.items
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeRole, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeRole, db=getRootInterface().db)
return {"items": enriched, "pagination": None}
@ -677,7 +677,7 @@ def get_all_access(
return [r.model_dump() if hasattr(r, "model_dump") else r for r in items]
if paginationParams and hasattr(result, 'items'):
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeAccess, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeAccess, db=getRootInterface().db)
return {
"items": enriched,
"pagination": PaginationMetadata(
@ -690,7 +690,7 @@ def get_all_access(
).model_dump(),
}
items = result if isinstance(result, list) else result.items
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeAccess, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeAccess, db=getRootInterface().db)
return {"items": enriched, "pagination": None}
@ -827,7 +827,7 @@ def get_contracts(
return [r.model_dump() if hasattr(r, "model_dump") else r for r in items]
if paginationParams and hasattr(result, 'items'):
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeContract, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(result.items), TrusteeContract, db=getRootInterface().db)
return {
"items": enriched,
"pagination": PaginationMetadata(
@ -840,7 +840,7 @@ def get_contracts(
).model_dump(),
}
items = result if isinstance(result, list) else result.items
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeContract, db=interface.db)
enriched = enrichRowsWithFkLabels(_toDicts(items), TrusteeContract, db=getRootInterface().db)
return {"items": enriched, "pagination": None}
@ -953,6 +953,7 @@ def get_documents(
context: RequestContext = Depends(getRequestContext)
):
"""Get all documents (metadata only) with optional pagination."""
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
mandateId = _validateInstanceAccess(instanceId, context)
if mode in ("filterValues", "ids"):
@ -966,8 +967,9 @@ def get_documents(
return [r.model_dump() if hasattr(r, 'model_dump') else r for r in items]
if paginationParams and hasattr(result, 'items'):
enriched = enrichRowsWithFkLabels(_itemsToDicts(result.items), TrusteeDocument, db=getRootInterface().db)
return {
"items": _itemsToDicts(result.items),
"items": enriched,
"pagination": PaginationMetadata(
currentPage=paginationParams.page or 1,
pageSize=paginationParams.pageSize or 20,
@ -978,7 +980,8 @@ def get_documents(
).model_dump(),
}
items = result if isinstance(result, list) else result.items
return {"items": _itemsToDicts(items), "pagination": None}
enriched = enrichRowsWithFkLabels(_itemsToDicts(items), TrusteeDocument, db=getRootInterface().db)
return {"items": enriched, "pagination": None}
def _handleDocumentMode(instanceId, mandateId, mode, column, pagination, context):
@ -991,7 +994,7 @@ def _handleDocumentMode(instanceId, mandateId, mode, column, pagination, context
raise HTTPException(status_code=400, detail="column parameter required for mode=filterValues")
result = interface.getAllDocuments(None)
items = [r.model_dump() if hasattr(r, 'model_dump') else r for r in (result.items if hasattr(result, 'items') else result)]
enrichRowsWithFkLabels(items, TrusteeDocument, db=interface.db)
enrichRowsWithFkLabels(items, TrusteeDocument, db=getRootInterface().db)
return handleFilterValuesInMemory(items, column, pagination)
if mode == "ids":
result = interface.getAllDocuments(None)
@ -1229,6 +1232,7 @@ def get_positions(
context: RequestContext = Depends(getRequestContext)
):
"""Get all positions with optional pagination."""
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
mandateId = _validateInstanceAccess(instanceId, context)
if mode in ("filterValues", "ids"):
@ -1241,9 +1245,12 @@ def get_positions(
def _itemsToDicts(items):
return [r.model_dump() if hasattr(r, 'model_dump') else r for r in items]
featureResolvers = _buildFeatureInternalResolvers(TrusteePosition, interface.db)
if paginationParams and hasattr(result, 'items'):
items = _itemsToDicts(result.items)
_enrichPositionsWithSyncStatus(items, interface, instanceId)
enrichRowsWithFkLabels(items, TrusteePosition, db=getRootInterface().db, extraResolvers=featureResolvers or None)
return {
"items": items,
"pagination": PaginationMetadata(
@ -1258,6 +1265,7 @@ def get_positions(
rawItems = result if isinstance(result, list) else result.items
items = _itemsToDicts(rawItems)
_enrichPositionsWithSyncStatus(items, interface, instanceId)
enrichRowsWithFkLabels(items, TrusteePosition, db=getRootInterface().db, extraResolvers=featureResolvers or None)
return {"items": items, "pagination": None}
@ -1273,7 +1281,7 @@ def _handlePositionMode(instanceId, mandateId, mode, column, pagination, context
result = interface.getAllPositions(None)
items = [r.model_dump() if hasattr(r, 'model_dump') else r for r in (result.items if hasattr(result, 'items') else result)]
_enrichPositionsWithSyncStatus(items, interface, instanceId)
enrichRowsWithFkLabels(items, TrusteePositionView, db=interface.db)
enrichRowsWithFkLabels(items, TrusteePositionView, db=getRootInterface().db)
return handleFilterValuesInMemory(items, column, pagination)
if mode == "ids":
result = interface.getAllPositions(None)
@ -2075,7 +2083,7 @@ def _paginatedReadEndpoint(
rawItems = result.items if hasattr(result, "items") else result
items = [r.model_dump() if hasattr(r, "model_dump") else r for r in rawItems]
featureResolvers = _buildFeatureInternalResolvers(modelClass, interface.db)
enrichRowsWithFkLabels(items, modelClass, db=interface.db, extraResolvers=featureResolvers or None)
enrichRowsWithFkLabels(items, modelClass, db=getRootInterface().db, extraResolvers=featureResolvers or None)
return handleFilterValuesInMemory(items, column, pagination)
if mode == "ids":
@ -2113,7 +2121,7 @@ def _paginatedReadEndpoint(
if paginationParams and hasattr(result, "items"):
enriched = enrichRowsWithFkLabels(
_itemsToDicts(result.items), modelClass,
db=interface.db, extraResolvers=featureResolvers or None,
db=getRootInterface().db, extraResolvers=featureResolvers or None,
)
return {
"items": enriched,
@ -2129,7 +2137,7 @@ def _paginatedReadEndpoint(
items = result.items if hasattr(result, "items") else result
enriched = enrichRowsWithFkLabels(
_itemsToDicts(items), modelClass,
db=interface.db, extraResolvers=featureResolvers or None,
db=getRootInterface().db, extraResolvers=featureResolvers or None,
)
return {"items": enriched, "pagination": None}

View file

@ -472,12 +472,13 @@ def list_feature_instances(
items = [inst.model_dump() for inst in instances]
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
from modules.datamodels.datamodelFeatures import FeatureInstance
enrichRowsWithFkLabels(items, FeatureInstance, db=rootInterface.db)
if mode == "filterValues":
if not column:
raise HTTPException(status_code=400, detail="column parameter required for mode=filterValues")
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
from modules.datamodels.datamodelFeatures import FeatureInstance
enrichRowsWithFkLabels(items, FeatureInstance, db=rootInterface.db)
return handleFilterValuesInMemory(items, column, pagination)
if mode == "ids":

View file

@ -940,6 +940,8 @@ def list_roles(
if paginationParams:
from modules.dbHelpers.paginationHelpers import applyFiltersAndSort
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
enrichRowsWithFkLabels(result, Role, db=interface.db)
sortedResult = applyFiltersAndSort(result, paginationParams)
totalItems = len(sortedResult)
totalPages = math.ceil(totalItems / paginationParams.pageSize) if totalItems > 0 else 0
@ -959,7 +961,8 @@ def list_roles(
)
)
else:
# No pagination - return all roles
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
enrichRowsWithFkLabels(result, Role, db=interface.db)
return PaginatedResponse(
items=result,
pagination=None

View file

@ -363,6 +363,16 @@ async def getNeutralizationMappings(
_enrichUserAndInstanceLabels(items, context)
fileIds = list({r.get("fileId") for r in items if r.get("fileId")})
if fileIds:
from modules.dbHelpers.fkLabelResolver import resolveFileLabels
from modules.interfaces.interfaceDbApp import getRootInterface
fileMap = resolveFileLabels(getRootInterface().db, fileIds)
for r in items:
fid = r.get("fileId")
if fid and fid in fileMap:
r["fileIdLabel"] = fileMap[fid] or fid
if mode == "filterValues" and column:
items = _applySortFilterSearch(items, filtersJson=filters)
return _distinctColumnValues(items, column)

View file

@ -120,7 +120,7 @@ def get_prompts(
def _promptsToEnrichedDicts(promptItems):
dicts = [r.model_dump() if hasattr(r, 'model_dump') else (dict(r) if not isinstance(r, dict) else r) for r in promptItems]
enrichRowsWithFkLabels(dicts, Prompt, db=managementInterface.db)
enrichRowsWithFkLabels(dicts, Prompt, db=getAppInterface(currentUser).db)
return dicts
managementInterface = interfaceDbManagement.getInterface(currentUser)

View file

@ -514,6 +514,10 @@ def getAllSubscriptions(
raise HTTPException(status_code=400, detail=f"Invalid pagination parameter: {str(e)}")
enriched = _buildEnrichedSubscriptions()
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
from modules.datamodels.datamodelSubscription import MandateSubscription
from modules.interfaces.interfaceDbApp import getRootInterface as _getRootIf
enrichRowsWithFkLabels(enriched, MandateSubscription, db=_getRootIf().db)
filtered = applyFiltersAndSort(enriched, paginationParams)
if paginationParams:

View file

@ -63,6 +63,8 @@ async def _listWorkflows(
pagination: Optional[str] = Query(default=None),
mandateId: Optional[str] = Query(default=None),
):
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
from modules.interfaces.interfaceDbApp import getRootInterface as _getRootIface
db = _getWorkflowAutomationDb()
try:
db._ensureTableExists(AutoWorkflow)
@ -76,6 +78,7 @@ async def _listWorkflows(
params = _parsePaginationOr400(pagination)
records = db.getRecordset(AutoWorkflow, recordFilter=scopeFilter)
enrichRowsWithFkLabels(records or [], AutoWorkflow, db=_getRootIface().db)
if params:
filtered = applyFiltersAndSort(records or [], params)
pageItems, totalItems = paginateInMemory(filtered, params)
@ -169,6 +172,8 @@ async def _listRuns(
mandateId: Optional[str] = Query(default=None),
workflowId: Optional[str] = Query(default=None),
):
from modules.dbHelpers.fkLabelResolver import enrichRowsWithFkLabels
from modules.interfaces.interfaceDbApp import getRootInterface as _getRootIface
db = _getWorkflowAutomationDb()
try:
db._ensureTableExists(AutoRun)
@ -185,6 +190,15 @@ async def _listRuns(
params = _parsePaginationOr400(pagination)
records = db.getRecordset(AutoRun, recordFilter=scopeFilter)
def _resolveWorkflowLabels(ids):
wfRecs = db.getRecordset(AutoWorkflow, recordFilter={"id": list(set(ids))}) or []
return {r.get("id"): r.get("label") or r.get("name") for r in wfRecs}
enrichRowsWithFkLabels(
records or [], AutoRun, db=_getRootIface().db,
extraResolvers={"workflowId": _resolveWorkflowLabels},
)
if params:
filtered = applyFiltersAndSort(records or [], params)
pageItems, totalItems = paginateInMemory(filtered, params)
@ -991,7 +1005,7 @@ def _getMetrics(
try:
workflows = db.getRecordset(AutoWorkflow, recordFilter=scopeFilter) or [] if db._ensureTableExists(AutoWorkflow) else []
wfIds = [w.get("id") for w in workflows]
runFilter = {"workflowId": {"$in": wfIds}} if wfIds else {"workflowId": "__none__"}
runFilter = {"workflowId": wfIds} if wfIds else {"workflowId": "__none__"}
runs = db.getRecordset(AutoRun, recordFilter=runFilter) or [] if db._ensureTableExists(AutoRun) else []
tasks = db.getRecordset(AutoTask, recordFilter=runFilter) or [] if db._ensureTableExists(AutoTask) else []
finally:
@ -1275,7 +1289,8 @@ def _getRunDetail(
if tid:
try:
from modules.dbHelpers.fkLabelResolver import resolveInstanceLabels
labelMap = resolveInstanceLabels(db, [tid])
from modules.interfaces.interfaceDbApp import getRootInterface as _getRootIface
labelMap = resolveInstanceLabels(_getRootIface().db, [tid])
targetInstanceLabel = labelMap.get(tid)
except Exception:
pass
@ -1465,6 +1480,85 @@ async def _executeWorkflow(
return result
@router.post("/execute")
@limiter.limit("30/minute")
async def _executeWorkflowFromBody(
request: Request,
body: dict = Body(..., description="{ workflowId?, graph?, targetInstanceId?, payload?, runEnvelope? }"),
context: RequestContext = Depends(getRequestContext),
) -> dict:
"""Execute a workflow — workflowId from body or ad-hoc graph execution."""
from modules.workflowAutomation.mainWorkflowAutomation import _getWorkflowAutomationServices
from modules.workflowAutomation.engine.executionEngine import executeGraph
from modules.interfaces.interfaceWorkflowAutomation import _getWorkflowAutomationInterface
from modules.workflows.processing.shared.methodDiscovery import discoverMethods
userId = str(context.user.id) if context.user else None
workflowId = body.get("workflowId") or ""
targetInstanceId = body.get("targetInstanceId") or ""
wf = None
if workflowId:
db = _getWorkflowAutomationDb()
try:
db._ensureTableExists(AutoWorkflow)
wf = db.getRecord(AutoWorkflow, workflowId)
finally:
db.close()
if not wf:
raise HTTPException(status_code=404, detail=routeApiMsg("Workflow not found"))
_validateWorkflowAccess(context, wf, "execute")
mandateId = (wf.get("mandateId") if wf else None) or str(context.mandateId or "")
instanceId = (wf.get("featureInstanceId") if wf else None) or targetInstanceId or str(context.featureInstanceId or "")
targetFeatureInstanceId = (wf.get("targetFeatureInstanceId") if wf else None) or targetInstanceId or ""
services = _getWorkflowAutomationServices(
context.user,
mandateId=mandateId,
featureInstanceId=instanceId,
)
discoverMethods(services)
graph = body.get("graph") or body.get("payload") or {}
if wf and not (graph.get("nodes") or []):
graph = wf.get("graph") or {}
logger.info(
"workflowAutomation /execute: workflowId=%s nodes=%d userId=%s",
workflowId, len(graph.get("nodes") or []), userId,
)
workflowForEnvelope = wf
runEnv = _buildExecuteRunEnvelope(
body,
workflowForEnvelope,
userId,
getattr(context.user, "language", None) if context.user else None,
)
wfLabel = (wf.get("label") if wf else None) or ""
iface = _getWorkflowAutomationInterface(context.user, mandateId, instanceId)
result = await executeGraph(
graph=graph,
services=services,
workflowId=workflowId or None,
instanceId=instanceId,
userId=userId,
mandateId=mandateId,
automation2_interface=iface,
run_envelope=runEnv,
label=wfLabel,
targetFeatureInstanceId=targetFeatureInstanceId,
)
logger.info(
"workflowAutomation /execute result: success=%s error=%s paused=%s",
result.get("success"), result.get("error"), result.get("paused"),
)
_startEmailPollerIfNeeded(result)
return result
# ---------------------------------------------------------------------------
# Version management
# ---------------------------------------------------------------------------

View file

@ -1003,6 +1003,9 @@ class ChatService:
"""Get workflow by ID by delegating to the chat interface"""
try:
logger.debug(f"getWorkflow called with workflowId: {workflowId}")
if workflowId.startswith("transient-"):
logger.debug(f"getWorkflow: skipping DB lookup for transient workflow {workflowId}")
return None
result = self.interfaceDbChat.getWorkflow(workflowId)
if result:
logger.debug(f"getWorkflow returned workflow with ID: {result.id}")

View file

@ -21,7 +21,7 @@ model (see wiki/b-reference/platform/unified-data-bar.md):
- FdsRecordNode (+children)-- feature-owned FeatureDataSource records
- FdsFieldNode -- virtual per-column nodes under fdsTable
The classes use `_inheritFlags.py` as a helper module for the actual
The classes use `modules/serviceCenter/core/flagResolution.py` as a helper module for the actual
walk/aggregate/cascade arithmetic, so the inheritance semantics live in
one place. The classes themselves only express "what does this node type
DO" -- ownership, RBAC, persistence routing, child enumeration.

View file

@ -770,7 +770,7 @@ async def executeGraph(
waFileLogger: Optional[RunFileLogger] = None
nodeOutputs: Dict[str, Any] = dict(initialNodeOutputs or {})
if not runId and automation2_interface and workflowId and not is_resume:
if not runId and automation2_interface and not is_resume:
run_context = {
"connectionMap": connectionMap,
"inputSources": inputSources,

View file

@ -599,9 +599,9 @@ class ActionNodeExecutor:
logger.exception("ActionNodeExecutor node %s FAILED: %s", nodeId, e)
return _normalizeError(e, outputSchema)
finally:
if chatService:
if self.services.chat:
try:
chatService.progressLogFinish(nodeOperationId, actionSuccess)
self.services.chat.progressLogFinish(nodeOperationId, actionSuccess)
except Exception:
pass
@ -630,11 +630,11 @@ class ActionNodeExecutor:
rawBytes = coerceDocumentDataToBytes(rawData)
if isinstance(dumped, dict) and rawBytes:
try:
_mgmt = self.services.interfaceDbComponent
_chatSvc = self.services.chat
_docName = dumped.get("documentName") or f"workflow-result-{nodeId}.bin"
_mimeType = dumped.get("mimeType") or "application/octet-stream"
_fileItem = _mgmt.createFile(_docName, _mimeType, rawBytes, folderId=persist_folder_id)
_mgmt.createFileData(_fileItem.id, rawBytes)
_fileItem = _chatSvc.createFile(_docName, _mimeType, rawBytes, folderId=persist_folder_id)
_chatSvc.createFileData(_fileItem.id, rawBytes)
dumped["fileId"] = _fileItem.id
dumped["id"] = _fileItem.id
dumped["fileName"] = _fileItem.fileName
@ -656,7 +656,7 @@ class ActionNodeExecutor:
"documents": docsList,
"count": len(docsList),
}
_attachConnectionProvenance(list_out, resolvedParams, outputSchema, chatService, self.services)
_attachConnectionProvenance(list_out, resolvedParams, outputSchema, self.services.chat, self.services)
return normalizeToSchema(list_out, outputSchema)
extractedContext = ""
@ -751,7 +751,7 @@ class ActionNodeExecutor:
"mode": data_dict.get("mode", resolvedParams.get("mode", "summarize")),
"count": int(data_dict.get("count", 0)),
}
_attachConnectionProvenance(cr_out, resolvedParams, outputSchema, chatService, self.services)
_attachConnectionProvenance(cr_out, resolvedParams, outputSchema, self.services.chat, self.services)
return normalizeToSchema(cr_out, outputSchema)
if nodeDef.get("popDocumentsFromOutput"):
@ -760,7 +760,7 @@ class ActionNodeExecutor:
if outputSchema in ("AiResult", "ActionResult") and result.success:
_attach_unified_presentation_data(out, node_def=nodeDef)
_attachConnectionProvenance(out, resolvedParams, outputSchema, chatService, self.services)
_attachConnectionProvenance(out, resolvedParams, outputSchema, self.services.chat, self.services)
# When the node declares ``surfaceDataAsTopLevel`` (typical for
# dynamic-schema context nodes whose output keys are graph-defined),

View file

@ -1194,8 +1194,8 @@ def _persist_extracted_image_parts(
)
return content_extracted_serial, artifacts
if services and hasattr(services, "interfaceDbComponent"):
mgmt = services.interfaceDbComponent
if services and hasattr(services, "chat"):
mgmt = services.chat
else:
from modules.interfaces.interfaceDbManagement import getInterface as _get_mgmt
from modules.security.rootAccess import getRootUser
@ -1206,7 +1206,7 @@ def _persist_extracted_image_parts(
return content_extracted_serial, artifacts
if not mgmt:
logger.warning("extractContent image persist: no interfaceDbComponent available")
logger.warning("extractContent image persist: no chat service available")
return content_extracted_serial, artifacts
stem = re.sub(r"[^\w\-]+", "_", name_stem).strip("_") or "extract"
@ -1310,11 +1310,11 @@ _IMAGE_MAX_DIMENSION = 1200
def _get_mgmt_for_presentation_render(services: Any) -> Optional[Any]:
mgmt = getattr(services, "interfaceDbComponent", None) if services else None
if mgmt:
return mgmt
if not services:
return None
chat = getattr(services, "chat", None)
if chat:
return chat
try:
import modules.interfaces.interfaceDbManagement as iface
@ -1385,7 +1385,7 @@ def _load_image_bytes_by_file_id(services: Any, file_id: str) -> Optional[bytes]
if not mgmt or not hasattr(mgmt, "getFileData"):
raise ValueError(
"no management interface available to load persisted image bytes — "
"services.interfaceDbComponent / mandate / instance must be set"
"services.chat / mandate / instance must be set"
)
return mgmt.getFileData(str(file_id))

View file

@ -17,7 +17,7 @@ import unittest
from typing import List
from unittest.mock import MagicMock
from modules.serviceCenter.services.serviceKnowledge import _inheritFlags
from modules.serviceCenter.core import flagResolution as _inheritFlags
def _ds(idVal: str, path: str, **flags) -> dict: