doc platform
|
|
@ -1 +0,0 @@
|
|||
<mxfile host="Electron" modified="2025-12-07T12:20:24.090Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.3.0 Chrome/104.0.5112.114 Electron/20.1.3 Safari/537.36" etag="cr0QP01cfDrbV7QtecK0" version="20.3.0" type="device"><diagram name="Module Dependencies" id="module-dependencies">7Zpbc9o6EMc/DXOe2pHxBfKYQC+Z0860k/Zc+tIR0hq7CIvKcoB++koggS+iTVrHcOA4D0G7siz/f9JqZbvnj+arVwIvkrecAuv1EV31/HGv3/e8KFD/tGVtLeHV1jIVKTW2veEu/QbGiIy1SCnklYqScybTRdVIeJYBkRUbFoIvq9VizqpXXeApNAx3BLOm9e+UysRYA4T2jteQThN7aWQ9c2xrG0OeYMqXJZP/ouePBOdy+2u+GgHT8llhtue9PODd9UxAJh9ywugr//yJfi7e/3kl3/8lrpMP3tdn/W0r95gV5o6Nklzkuqltz+Xa6iF4kVHQLXo9/2aZpBLuFpho71INAWVL5JwZd5wyNuKMi825Png0hIGy51LwGZQ8V9HAx9HOY5Xu6zZ4Ju/M9T1b3g4TL1Bl038QElYHhfF2cquRCnwOUqxVFXPCDqYZpEFgyssScUsxKcGOjA2bQTbdNb3HoH4YEo+g4jWofLz9I28VBsUwjIkLRkSGMIlPBUa/QxY4JVxAQ3trbnc2xDFMAFwAJgOPeOQ3ADTUdjA5CGAfwdY1Il0gyEHcpwTyBoS9o+Wg5JOhukEHhht0FZY8j8Ggy6WW0OZoB8+wPj/8DumkmQQRYxefsqtdQhFGKI5dhPwBQqNRG4TizdEOofCY82f2NhjLCN++ng3XHwmhkyIunoUNHkBVlmOKGc90GKoi4kImfMozzN5wvjDGLyDl2qiIC8mr2JRiYv2PKiBb+FcXnoe2OF6VneO1KVWxUohxweSmFr3WGZwyThgnMydnVellymwXDtLLeSHILpKQQqRyKyuSWExBuoKP1uiHvAUwLNP7aqb4e8Gv3LNK8LOOy1iDomMGuRiwLIQjxO0dlx3gGhlC1CGdJRezmOl9XR1PyXPZKUKDT6fTRyktHZPHmttFg1CIwDl1EPLGNyc3deq5QZczR69nUYPLT3D8Qh7gXKTrK3l13f5SzBeWCBbk4St5eb0ur+TlnVz367i+4vBClK5vCY6jttd8YvJflrsu6mkNbe/cxvah/cBpjG2r7pnIXU2vT2n7tRE7uBCxa3n0kdQOz0rtmqanFbX7g0vR+hTiiN1xnYna5U1dWepqfDmS1OeV+x2S+jSSEf+8kpFDYldzwiNJ7V+I1MeN1s4X8E3pKZZ4rr9vaf0ZVhzqP9czrGhztPEMa3v0nuQNfeh4RT/o9HOJZs6eJ+p26P+k6k8bj03qAfm+aiVd5KC1TPBCGwnjBf05rCcQLHAM7cihV/BUejVz9ut3tz+QrAOJhrXZ31QodCjkP14hVdx/HLfxlT4y9F98Bw==</diagram></mxfile>
|
||||
683
appdoc/doc_workflow_actions_rbac_concept.md
Normal file
|
|
@ -0,0 +1,683 @@
|
|||
# Workflow Actions RBAC Integration Concept
|
||||
|
||||
## Übersicht
|
||||
|
||||
Dieses Dokument beschreibt das Konzept für die Umstrukturierung der Workflow Actions, um:
|
||||
1. **RBAC-Integration** zu ermöglichen (Schutz von Actions über RESOURCE-Context)
|
||||
2. **Strukturierte Parameter-Definitionen** statt Docstrings zu verwenden
|
||||
3. **UI-Rendering-Typen** für Parameter zu definieren
|
||||
4. **Keine Duplikation** von Parameter-Definitionen zu haben
|
||||
5. **Plug-and-Play** Funktionalität beizubehalten
|
||||
|
||||
## Architektur-Konzept
|
||||
|
||||
### Grundprinzip: Deklarative Action-Definition
|
||||
|
||||
Ähnlich wie bei `aicore` Models, wo eine Struktur definiert wird und die Funktion ein Attribut ist, werden Actions jetzt deklarativ definiert:
|
||||
|
||||
```python
|
||||
class MethodOutlook(MethodBase):
|
||||
def __init__(self, services):
|
||||
super().__init__(services)
|
||||
self.name = "outlook"
|
||||
self.description = "Handle Microsoft Outlook email operations"
|
||||
|
||||
# Actions werden deklarativ definiert
|
||||
self._actions = {
|
||||
"readEmails": ActionDefinition(
|
||||
actionId="outlook.readEmails", # Für RBAC: RESOURCE context
|
||||
description="Read emails from Outlook mailbox",
|
||||
parameters={
|
||||
"connectionReference": ActionParameter(
|
||||
name="connectionReference",
|
||||
type=str,
|
||||
frontendType="select",
|
||||
frontendOptions="user.connection",
|
||||
required=True,
|
||||
description="Microsoft connection label"
|
||||
),
|
||||
"query": ActionParameter(
|
||||
name="query",
|
||||
type=str,
|
||||
frontendType="text",
|
||||
required=False,
|
||||
description="Search query for emails"
|
||||
),
|
||||
"folder": ActionParameter(
|
||||
name="folder",
|
||||
type=str,
|
||||
frontendType="select",
|
||||
frontendOptions="outlook.folder",
|
||||
required=False,
|
||||
description="Folder name (e.g., 'Inbox', 'Drafts')"
|
||||
),
|
||||
"limit": ActionParameter(
|
||||
name="limit",
|
||||
type=int,
|
||||
frontendType="number",
|
||||
required=False,
|
||||
default=50,
|
||||
description="Maximum number of emails to return"
|
||||
)
|
||||
},
|
||||
execute=self._executeReadEmails # Funktion als Attribut
|
||||
),
|
||||
"sendEmail": ActionDefinition(
|
||||
actionId="outlook.sendEmail",
|
||||
description="Send email via Outlook",
|
||||
parameters={...},
|
||||
execute=self._executeSendEmail
|
||||
)
|
||||
}
|
||||
|
||||
async def _executeReadEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||
"""Execute function - keine Parameter-Definition mehr hier"""
|
||||
# Implementation...
|
||||
```
|
||||
|
||||
## Globale Frontend-Type-Definition
|
||||
|
||||
**WICHTIG**: Frontend-Types werden zentral in `modules/shared/frontendTypes.py` definiert, nicht redundant pro Action.
|
||||
|
||||
Die globale `FrontendType` Enum enthält:
|
||||
- **Standard Types**: `text`, `textarea`, `number`, `select`, `multiselect`, `checkbox`, `date`, `datetime`, `email`, `timestamp`, `json`, `multilingual`, `file`
|
||||
- **Custom Types für Actions**: `userConnection`, `documentReference`, `workflowAction`
|
||||
|
||||
Custom-Types unterstützen dynamische Option-Listen über API-Endpoints:
|
||||
- `userConnection` → `/api/options/user.connection` (Connections des aktuellen Users)
|
||||
- `documentReference` → `/api/options/workflow.documentReference` (Document-Referenzen aus Workflow-Context)
|
||||
- `workflowAction` → `/api/options/workflow.action` (Verfügbare Actions aus Workflow-Context)
|
||||
|
||||
## Datenmodelle
|
||||
|
||||
### ActionParameter
|
||||
|
||||
**WICHTIG**: Frontend-Types werden global definiert in `modules/shared/frontendTypes.py` und nicht redundant in Actions.
|
||||
|
||||
```python
|
||||
from typing import Optional, Any, Union, List, Dict
|
||||
from pydantic import BaseModel, Field
|
||||
from modules.shared.frontendTypes import FrontendType # Globale Definition
|
||||
|
||||
class ActionParameter(BaseModel):
|
||||
"""Parameter definition for an action"""
|
||||
name: str = Field(description="Parameter name")
|
||||
type: str = Field(description="Python type as string (e.g., 'str', 'int', 'bool', 'List[str]')")
|
||||
frontendType: FrontendType = Field(description="UI rendering type (from global FrontendType enum)")
|
||||
frontendOptions: Optional[Union[str, List[Dict[str, Any]]]] = Field(
|
||||
None,
|
||||
description="Options for select/multiselect/custom types. String reference (e.g., 'user.connection') or static list. For custom types like userConnection, this is automatically set to the API endpoint."
|
||||
)
|
||||
required: bool = Field(False, description="Whether parameter is required")
|
||||
default: Optional[Any] = Field(None, description="Default value")
|
||||
description: str = Field("", description="Parameter description")
|
||||
validation: Optional[Dict[str, Any]] = Field(
|
||||
None,
|
||||
description="Validation rules (e.g., {'min': 1, 'max': 100})"
|
||||
)
|
||||
```
|
||||
|
||||
**Custom Frontend Types**:
|
||||
- `FrontendType.USER_CONNECTION`: User connection selector - dynamische Options von `/api/options/user.connection`
|
||||
- `FrontendType.DOCUMENT_REFERENCE`: Document reference selector - dynamische Options aus Workflow-Context
|
||||
- `FrontendType.WORKFLOW_ACTION`: Workflow action selector - dynamische Options aus verfügbaren Actions
|
||||
|
||||
Für Custom-Types wird `frontendOptions` automatisch auf den entsprechenden API-Endpoint gesetzt (z.B. `"user.connection"`).
|
||||
|
||||
### ActionDefinition
|
||||
|
||||
```python
|
||||
from typing import Dict, Callable, Awaitable
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class ActionDefinition(BaseModel):
|
||||
"""Complete definition of an action"""
|
||||
actionId: str = Field(
|
||||
description="Unique action identifier for RBAC (format: 'module.actionName', e.g., 'outlook.readEmails')"
|
||||
)
|
||||
description: str = Field(description="Action description")
|
||||
parameters: Dict[str, ActionParameter] = Field(
|
||||
default_factory=dict,
|
||||
description="Parameter definitions"
|
||||
)
|
||||
execute: Callable[[Dict[str, Any]], Awaitable[ActionResult]] = Field(
|
||||
description="Execution function - async function that takes parameters dict and returns ActionResult"
|
||||
)
|
||||
# Optional metadata
|
||||
category: Optional[str] = Field(None, description="Action category for grouping")
|
||||
tags: List[str] = Field(default_factory=list, description="Tags for search/filtering")
|
||||
```
|
||||
|
||||
## MethodBase Erweiterung
|
||||
|
||||
### Neue MethodBase Struktur
|
||||
|
||||
```python
|
||||
class MethodBase:
|
||||
"""Base class for all methods"""
|
||||
|
||||
def __init__(self, services: Any):
|
||||
self.services = services
|
||||
self.name: str
|
||||
self.description: str
|
||||
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
||||
|
||||
# Actions werden als Dictionary definiert
|
||||
self._actions: Dict[str, ActionDefinition] = {}
|
||||
|
||||
# Nach Initialisierung: Actions registrieren
|
||||
self._registerActions()
|
||||
|
||||
def _registerActions(self):
|
||||
"""Register all actions defined in _actions"""
|
||||
# Kann überschrieben werden für dynamische Registrierung
|
||||
pass
|
||||
|
||||
@property
|
||||
def actions(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Dynamically collect all actions from _actions dictionary.
|
||||
Returns format compatible with existing system.
|
||||
"""
|
||||
result = {}
|
||||
for actionName, actionDef in self._actions.items():
|
||||
# RBAC-Check: Prüfe ob Action für aktuellen User verfügbar ist
|
||||
if not self._checkActionPermission(actionDef.actionId):
|
||||
continue # Skip if user doesn't have permission
|
||||
|
||||
# Konvertiere ActionDefinition zu altem Format für Kompatibilität
|
||||
result[actionName] = {
|
||||
'description': actionDef.description,
|
||||
'parameters': self._convertParametersToOldFormat(actionDef.parameters),
|
||||
'method': self._createActionWrapper(actionDef)
|
||||
}
|
||||
return result
|
||||
|
||||
def _checkActionPermission(self, actionId: str) -> bool:
|
||||
"""
|
||||
Check if current user has permission to execute this action.
|
||||
Uses RBAC RESOURCE context.
|
||||
"""
|
||||
if not hasattr(self.services, 'rbac') or not self.services.rbac:
|
||||
# Fallback: Allow if RBAC not available (backward compatibility)
|
||||
return True
|
||||
|
||||
currentUser = self.services.chat.getCurrentUser()
|
||||
if not currentUser:
|
||||
return False
|
||||
|
||||
# RBAC-Check: RESOURCE context, item = actionId
|
||||
permissions = self.services.rbac.getUserPermissions(
|
||||
user=currentUser,
|
||||
context=AccessRuleContext.RESOURCE,
|
||||
item=actionId
|
||||
)
|
||||
|
||||
return permissions.view
|
||||
|
||||
def _convertParametersToOldFormat(self, parameters: Dict[str, ActionParameter]) -> Dict[str, Dict[str, Any]]:
|
||||
"""Convert ActionParameter dict to old format for compatibility"""
|
||||
result = {}
|
||||
for paramName, param in parameters.items():
|
||||
result[paramName] = {
|
||||
'type': param.type,
|
||||
'required': param.required,
|
||||
'description': param.description,
|
||||
'default': param.default,
|
||||
'frontendType': param.frontendType.value,
|
||||
'frontendOptions': param.frontendOptions
|
||||
}
|
||||
return result
|
||||
|
||||
def _createActionWrapper(self, actionDef: ActionDefinition):
|
||||
"""Create wrapper function that matches old action signature"""
|
||||
async def wrapper(parameters: Dict[str, Any], *args, **kwargs):
|
||||
# Parameter-Validierung basierend auf ActionParameter definitions
|
||||
validatedParams = self._validateParameters(parameters, actionDef.parameters)
|
||||
|
||||
# Execute action
|
||||
return await actionDef.execute(validatedParams, *args, **kwargs)
|
||||
|
||||
wrapper.is_action = True
|
||||
return wrapper
|
||||
|
||||
def _validateParameters(self, parameters: Dict[str, Any], paramDefs: Dict[str, ActionParameter]) -> Dict[str, Any]:
|
||||
"""Validate parameters against definitions"""
|
||||
validated = {}
|
||||
|
||||
for paramName, paramDef in paramDefs.items():
|
||||
value = parameters.get(paramName)
|
||||
|
||||
# Check required
|
||||
if paramDef.required and value is None:
|
||||
raise ValueError(f"Required parameter '{paramName}' is missing")
|
||||
|
||||
# Use default if not provided
|
||||
if value is None and paramDef.default is not None:
|
||||
value = paramDef.default
|
||||
|
||||
# Type validation
|
||||
if value is not None:
|
||||
value = self._validateType(value, paramDef.type)
|
||||
|
||||
# Custom validation rules
|
||||
if paramDef.validation and value is not None:
|
||||
self._applyValidationRules(value, paramDef.validation)
|
||||
|
||||
validated[paramName] = value
|
||||
|
||||
return validated
|
||||
|
||||
def _validateType(self, value: Any, expectedType: type) -> Any:
|
||||
"""Validate and convert value to expected type"""
|
||||
# Type validation logic...
|
||||
if expectedType == int:
|
||||
return int(value)
|
||||
elif expectedType == str:
|
||||
return str(value)
|
||||
# ... weitere Typen
|
||||
return value
|
||||
|
||||
def _applyValidationRules(self, value: Any, rules: Dict[str, Any]):
|
||||
"""Apply custom validation rules"""
|
||||
if 'min' in rules and value < rules['min']:
|
||||
raise ValueError(f"Value must be >= {rules['min']}")
|
||||
if 'max' in rules and value > rules['max']:
|
||||
raise ValueError(f"Value must be <= {rules['max']}")
|
||||
# ... weitere Validierungsregeln
|
||||
```
|
||||
|
||||
## Migrationsstrategie
|
||||
|
||||
### Schritt 1: Neue Datenmodelle erstellen
|
||||
|
||||
**Datei**: `gateway/modules/datamodels/datamodelWorkflowActions.py`
|
||||
|
||||
```python
|
||||
from typing import Optional, Any, Union, List, Dict, Callable, Awaitable
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
from modules.datamodels.datamodelChat import ActionResult
|
||||
from modules.shared.frontendTypes import FrontendType # Globale Definition verwenden
|
||||
|
||||
class ActionParameter(BaseModel):
|
||||
"""Parameter definition for an action"""
|
||||
name: str
|
||||
type: str # String representation of type: "str", "int", "bool", "List[str]", etc.
|
||||
frontendType: FrontendType
|
||||
frontendOptions: Optional[Union[str, List[Dict[str, Any]]]] = None
|
||||
required: bool = False
|
||||
default: Optional[Any] = None
|
||||
description: str = ""
|
||||
validation: Optional[Dict[str, Any]] = None
|
||||
|
||||
class ActionDefinition(BaseModel):
|
||||
"""Complete definition of an action"""
|
||||
actionId: str # Format: "module.actionName" (e.g., "outlook.readEmails")
|
||||
description: str
|
||||
parameters: Dict[str, ActionParameter] = Field(default_factory=dict)
|
||||
execute: Optional[Callable] = None # Will be set dynamically
|
||||
category: Optional[str] = None
|
||||
tags: List[str] = Field(default_factory=list)
|
||||
```
|
||||
|
||||
### Schritt 2: MethodBase erweitern
|
||||
|
||||
**Datei**: `gateway/modules/workflows/methods/methodBase.py`
|
||||
|
||||
- Neue `_actions` Dictionary Property
|
||||
- RBAC-Check Integration
|
||||
- Parameter-Validierung
|
||||
- Kompatibilität mit bestehendem System
|
||||
|
||||
### Schritt 3: Beispiel-Migration
|
||||
|
||||
**Vorher** (methodOutlook.py):
|
||||
```python
|
||||
@action
|
||||
async def readEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||
"""
|
||||
GENERAL:
|
||||
- Purpose: Read emails from Outlook mailbox
|
||||
|
||||
Parameters:
|
||||
- connectionReference (str, required): Microsoft connection label.
|
||||
- query (str, optional): Search query for emails.
|
||||
- folder (str, optional): Folder name.
|
||||
- limit (int, optional): Maximum number of emails. Default: 50.
|
||||
"""
|
||||
# Implementation...
|
||||
```
|
||||
|
||||
**Nachher** (methodOutlook.py):
|
||||
```python
|
||||
def __init__(self, services):
|
||||
super().__init__(services)
|
||||
self.name = "outlook"
|
||||
self.description = "Handle Microsoft Outlook email operations"
|
||||
|
||||
self._actions = {
|
||||
"readEmails": ActionDefinition(
|
||||
actionId="outlook.readEmails",
|
||||
description="Read emails from Outlook mailbox",
|
||||
parameters={
|
||||
"connectionReference": ActionParameter(
|
||||
name="connectionReference",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_CONNECTION, # Custom type - automatisch API-Endpoint
|
||||
required=True,
|
||||
description="Microsoft connection label"
|
||||
),
|
||||
"query": ActionParameter(
|
||||
name="query",
|
||||
type="str",
|
||||
frontendType=FrontendType.TEXT,
|
||||
required=False,
|
||||
description="Search query for emails"
|
||||
),
|
||||
"folder": ActionParameter(
|
||||
name="folder",
|
||||
type="str",
|
||||
frontendType=FrontendType.SELECT,
|
||||
frontendOptions="outlook.folder",
|
||||
required=False,
|
||||
description="Folder name (e.g., 'Inbox', 'Drafts')"
|
||||
),
|
||||
"limit": ActionParameter(
|
||||
name="limit",
|
||||
type="int",
|
||||
frontendType=FrontendType.NUMBER,
|
||||
required=False,
|
||||
default=50,
|
||||
description="Maximum number of emails to return",
|
||||
validation={"min": 1, "max": 1000}
|
||||
)
|
||||
},
|
||||
execute=self._executeReadEmails
|
||||
)
|
||||
}
|
||||
|
||||
async def _executeReadEmails(self, parameters: Dict[str, Any]) -> ActionResult:
|
||||
"""Execute function - keine Parameter-Definition mehr hier"""
|
||||
# Implementation bleibt gleich...
|
||||
```
|
||||
|
||||
## RBAC-Integration
|
||||
|
||||
### Action-IDs Format
|
||||
|
||||
Actions werden im RBAC-System als RESOURCE-Context Items behandelt:
|
||||
|
||||
- **Format**: `{moduleName}.{actionName}`
|
||||
- **Beispiele**:
|
||||
- `outlook.readEmails`
|
||||
- `outlook.sendEmail`
|
||||
- `sharepoint.uploadDocument`
|
||||
- `ai.process`
|
||||
|
||||
### RBAC-Regeln für Actions
|
||||
|
||||
```json
|
||||
{
|
||||
"roleLabel": "user",
|
||||
"context": "RESOURCE",
|
||||
"item": "outlook.readEmails",
|
||||
"view": true
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"roleLabel": "admin",
|
||||
"context": "RESOURCE",
|
||||
"item": "outlook",
|
||||
"view": true
|
||||
}
|
||||
```
|
||||
|
||||
**Hierarchie**: Spezifische Action-Regeln überschreiben generische Module-Regeln.
|
||||
|
||||
### Bootstrap: Default RBAC Rules für Actions
|
||||
|
||||
In `interfaceBootstrap.py`:
|
||||
|
||||
```python
|
||||
def initRbacRules(db: DatabaseConnector) -> None:
|
||||
# ... existing rules ...
|
||||
|
||||
# Action Rules (RESOURCE context)
|
||||
createActionRules(db)
|
||||
|
||||
def createActionRules(db: DatabaseConnector):
|
||||
"""Create default RBAC rules for workflow actions"""
|
||||
|
||||
# SysAdmin: Access to all actions
|
||||
db.recordCreate(AccessRule(
|
||||
roleLabel="sysadmin",
|
||||
context=AccessRuleContext.RESOURCE,
|
||||
item=None, # All resources
|
||||
view=True
|
||||
))
|
||||
|
||||
# Admin: Access to all actions
|
||||
db.recordCreate(AccessRule(
|
||||
roleLabel="admin",
|
||||
context=AccessRuleContext.RESOURCE,
|
||||
item=None,
|
||||
view=True
|
||||
))
|
||||
|
||||
# User: Access to specific actions only
|
||||
userActions = [
|
||||
"outlook.readEmails",
|
||||
"outlook.sendEmail",
|
||||
"sharepoint.readDocuments",
|
||||
"ai.process"
|
||||
]
|
||||
|
||||
for actionId in userActions:
|
||||
db.recordCreate(AccessRule(
|
||||
roleLabel="user",
|
||||
context=AccessRuleContext.RESOURCE,
|
||||
item=actionId,
|
||||
view=True
|
||||
))
|
||||
|
||||
# Viewer: Read-only actions
|
||||
viewerActions = [
|
||||
"outlook.readEmails",
|
||||
"sharepoint.readDocuments"
|
||||
]
|
||||
|
||||
for actionId in viewerActions:
|
||||
db.recordCreate(AccessRule(
|
||||
roleLabel="viewer",
|
||||
context=AccessRuleContext.RESOURCE,
|
||||
item=actionId,
|
||||
view=True
|
||||
))
|
||||
```
|
||||
|
||||
## Vorteile
|
||||
|
||||
### 1. Keine Duplikation
|
||||
- Parameter werden nur einmal definiert (in `ActionDefinition`)
|
||||
- Keine Docstring-Parsing mehr nötig
|
||||
- Type-Safety durch Pydantic Models
|
||||
|
||||
### 2. RBAC-Integration
|
||||
- Jede Action hat eine eindeutige ID für RBAC
|
||||
- Granulare Kontrolle pro Action möglich
|
||||
- Hierarchische Regeln (Module → Action)
|
||||
|
||||
### 3. UI-Rendering
|
||||
- Frontend-Typen explizit definiert
|
||||
- Options-Referenzen für dynamische Optionen
|
||||
- Validierung auf Backend-Ebene
|
||||
|
||||
### 4. Plug-and-Play
|
||||
- Actions bleiben als separate Method-Klassen
|
||||
- Einfache Erweiterung durch neue Method-Klassen
|
||||
- Kompatibilität mit bestehendem System
|
||||
|
||||
### 5. Type Safety
|
||||
- Pydantic Models für Validierung
|
||||
- Type-Hints für bessere IDE-Unterstützung
|
||||
- Runtime-Validierung
|
||||
|
||||
## Migration Timeline
|
||||
|
||||
### Phase 1: Foundation (Woche 1)
|
||||
- ✅ Datenmodelle erstellen (`datamodelWorkflowActions.py`)
|
||||
- ✅ MethodBase erweitern
|
||||
- ✅ RBAC-Integration in MethodBase
|
||||
|
||||
### Phase 2: Beispiel-Migration (Woche 2)
|
||||
- 📝 Ein Method-Beispiel migrieren (z.B. `methodAi.py`)
|
||||
- 📝 Tests schreiben
|
||||
- 📝 Dokumentation aktualisieren
|
||||
|
||||
### Phase 3: Vollständige Migration (Woche 3-4)
|
||||
- 📝 Alle Methods migrieren
|
||||
- 📝 RBAC-Regeln in Bootstrap erstellen
|
||||
- 📝 Frontend-Integration
|
||||
|
||||
### Phase 4: Testing & Cleanup (Woche 5)
|
||||
- 📝 Unit Tests
|
||||
- 📝 Integration Tests
|
||||
- 📝 Performance Tests
|
||||
- 📝 Alte Docstring-Parsing-Logik entfernen
|
||||
|
||||
## Offene Fragen
|
||||
|
||||
1. **Backward Compatibility**: Sollen alte Actions ohne `_actions` Dictionary weiterhin funktionieren?
|
||||
- **Antwort**: Ja, MethodBase prüft zuerst `_actions`, dann fallback auf `@action` Decorator
|
||||
|
||||
2. **Parameter-Validierung**: Soll Validierung strikt sein oder tolerant?
|
||||
- **Antwort**: Konfigurierbar pro Action
|
||||
|
||||
3. **Action-Discovery**: Sollen Actions zur Laufzeit registriert werden können?
|
||||
- **Antwort**: Ja, über `_registerActions()` Methode
|
||||
|
||||
4. **Frontend-Integration**: Wie werden Actions im Frontend angezeigt?
|
||||
- **Antwort**: API-Endpoint `/api/workflows/actions` liefert strukturierte Action-Definitionen
|
||||
|
||||
## API-Endpunkte
|
||||
|
||||
### GET /api/workflows/actions
|
||||
Liefert alle verfügbaren Actions für den aktuellen User (gefiltert nach RBAC):
|
||||
|
||||
```json
|
||||
{
|
||||
"actions": [
|
||||
{
|
||||
"module": "outlook",
|
||||
"actionId": "outlook.readEmails",
|
||||
"name": "readEmails",
|
||||
"description": "Read emails from Outlook mailbox",
|
||||
"parameters": {
|
||||
"connectionReference": {
|
||||
"type": "str",
|
||||
"frontendType": "userConnection",
|
||||
"frontendOptions": "user.connection", # Automatisch für Custom-Types
|
||||
"required": true,
|
||||
"description": "Microsoft connection label"
|
||||
},
|
||||
"documentList": {
|
||||
"type": "List[str]",
|
||||
"frontendType": "documentReference",
|
||||
"frontendOptions": "workflow.documentReference", # Automatisch für Custom-Types
|
||||
"required": false,
|
||||
"description": "Document list reference(s) from previous actions"
|
||||
},
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/workflows/actions/{module}
|
||||
Liefert Actions für ein spezifisches Modul.
|
||||
|
||||
### POST /api/workflows/actions/{module}/{action}/execute
|
||||
Führt eine Action aus (mit RBAC-Check).
|
||||
|
||||
## Custom Frontend Types für Actions
|
||||
|
||||
### Verfügbare Custom Types
|
||||
|
||||
1. **`FrontendType.USER_CONNECTION`**
|
||||
- **API-Endpoint**: `/api/options/user.connection`
|
||||
- **Beschreibung**: Zeigt alle aktiven Connections des aktuellen Users
|
||||
- **Verwendung**: Für Parameter wie `connectionReference` in Outlook/SharePoint Actions
|
||||
- **Beispiel**:
|
||||
```python
|
||||
ActionParameter(
|
||||
name="connectionReference",
|
||||
type="str",
|
||||
frontendType=FrontendType.USER_CONNECTION,
|
||||
required=True,
|
||||
description="Microsoft connection label"
|
||||
)
|
||||
```
|
||||
|
||||
2. **`FrontendType.DOCUMENT_REFERENCE`**
|
||||
- **API-Endpoint**: `/api/options/workflow.documentReference` (zu implementieren)
|
||||
- **Beschreibung**: Zeigt verfügbare Document-Referenzen aus dem aktuellen Workflow-Context
|
||||
- **Verwendung**: Für Parameter wie `documentList` in Actions, die auf vorherige Action-Ergebnisse verweisen
|
||||
- **Beispiel**:
|
||||
```python
|
||||
ActionParameter(
|
||||
name="documentList",
|
||||
type="List[str]",
|
||||
frontendType=FrontendType.DOCUMENT_REFERENCE,
|
||||
required=False,
|
||||
description="Document list reference(s) from previous actions"
|
||||
)
|
||||
```
|
||||
|
||||
3. **`FrontendType.WORKFLOW_ACTION`**
|
||||
- **API-Endpoint**: `/api/options/workflow.action` (zu implementieren)
|
||||
- **Beschreibung**: Zeigt verfügbare Actions aus dem Workflow-Context
|
||||
- **Verwendung**: Für Parameter, die auf andere Actions verweisen
|
||||
|
||||
### Custom Types erweitern
|
||||
|
||||
Neue Custom-Types können über `frontendTypes.py` registriert werden:
|
||||
|
||||
```python
|
||||
from modules.shared.frontendTypes import FrontendType, registerCustomType
|
||||
|
||||
# Neuer Custom-Type hinzufügen
|
||||
FrontendType.SHAREPOINT_FOLDER = "sharepointFolder"
|
||||
|
||||
# Registrieren
|
||||
registerCustomType(
|
||||
frontendType=FrontendType.SHAREPOINT_FOLDER,
|
||||
optionsApiEndpoint="sharepoint.folder",
|
||||
description={
|
||||
"en": "SharePoint Folder",
|
||||
"fr": "Dossier SharePoint",
|
||||
"de": "SharePoint-Ordner"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Frontend-Integration
|
||||
|
||||
Das Frontend muss:
|
||||
1. Custom-Types erkennen (z.B. `frontendType === "userConnection"`)
|
||||
2. Automatisch Options von `/api/options/{optionsName}` laden
|
||||
3. Die Options als Select/Multiselect rendern
|
||||
|
||||
**Beispiel Frontend-Logik**:
|
||||
```typescript
|
||||
if (param.frontendType === 'userConnection') {
|
||||
// Automatisch Options von /api/options/user.connection laden
|
||||
const options = await fetch(`/api/options/${param.frontendOptions}`);
|
||||
// Als Select rendern
|
||||
}
|
||||
```
|
||||
|
||||
BIN
platform_overview.zip
Normal file
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |