From 41e02b5a2cf5234cbe3b1fc54f7f81f6885adb3e Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Sat, 24 Jan 2026 02:06:54 +0100
Subject: [PATCH] fixed automation and trustee
---
src/api/automationApi.ts | 2 +
src/hooks/useAutomations.ts | 105 +++++++-----------------
src/hooks/useInstancePermissions.tsx | 5 ++
src/pages/workflows/AutomationsPage.tsx | 67 ++++++++++++---
4 files changed, 93 insertions(+), 86 deletions(-)
diff --git a/src/api/automationApi.ts b/src/api/automationApi.ts
index b192cd7..2004c0b 100644
--- a/src/api/automationApi.ts
+++ b/src/api/automationApi.ts
@@ -7,6 +7,7 @@ import { ApiRequestOptions } from '../hooks/useApi';
export interface Automation {
id: string;
mandateId: string;
+ featureInstanceId: string;
label: string;
template: string | object;
placeholders: Record;
@@ -51,6 +52,7 @@ export interface CreateAutomationRequest {
schedule?: string;
active?: boolean;
mandateId?: string;
+ featureInstanceId?: string;
}
export interface UpdateAutomationRequest {
diff --git a/src/hooks/useAutomations.ts b/src/hooks/useAutomations.ts
index ae44fda..7df5a6d 100644
--- a/src/hooks/useAutomations.ts
+++ b/src/hooks/useAutomations.ts
@@ -56,91 +56,33 @@ export function useAutomations() {
const { request, isLoading: loading, error } = useApiRequest();
const { checkPermission } = usePermissions();
- // Fallback attributes for automation form
- const fallbackAttributes: AttributeDefinition[] = [
- {
- name: 'label',
- type: 'text',
- label: 'Name',
- required: true,
- editable: true,
- visible: true,
- sortable: true,
- searchable: true,
- width: 200,
- },
- {
- name: 'schedule',
- type: 'text',
- label: 'Zeitplan (Cron)',
- required: false,
- editable: true,
- visible: true,
- description: 'z.B. "0 8 * * *" für täglich 8:00 Uhr',
- width: 150,
- },
- {
- name: 'template',
- type: 'textarea',
- label: 'Template',
- required: false,
- editable: true,
- visible: true,
- width: 200,
- },
- {
- name: 'active',
- type: 'checkbox',
- label: 'Aktiv',
- required: false,
- editable: true,
- visible: true,
- default: true,
- width: 80,
- },
- {
- name: 'status',
- type: 'text',
- label: 'Status',
- required: false,
- editable: false,
- visible: true,
- readonly: true,
- width: 100,
- },
- ];
-
- // Fetch attributes from backend
+ // Fetch attributes from backend - no fallback, errors should be visible
const fetchAttributes = useCallback(async () => {
try {
- const response = await api.get('/api/attributes/AutomationDefinition');
+ const response = await api.get('/api/automations/attributes');
let attrs: AttributeDefinition[] = [];
- if (response.data?.attributes && Array.isArray(response.data.attributes)) {
+ // Backend returns: { attributes: { model: "...", attributes: [...] } }
+ // So we need to access response.data.attributes.attributes
+ if (response.data?.attributes?.attributes && Array.isArray(response.data.attributes.attributes)) {
+ attrs = response.data.attributes.attributes;
+ } else if (response.data?.attributes && Array.isArray(response.data.attributes)) {
+ // Fallback: if attributes is directly an array
attrs = response.data.attributes;
} else if (Array.isArray(response.data)) {
attrs = response.data;
- } else if (response.data && typeof response.data === 'object') {
- const keys = Object.keys(response.data);
- for (const key of keys) {
- if (Array.isArray(response.data[key])) {
- attrs = response.data[key];
- break;
- }
- }
}
- // Use fallback if no attributes returned
if (attrs.length === 0) {
- attrs = fallbackAttributes;
+ console.warn('No attributes returned from backend for AutomationDefinition');
}
setAttributes(attrs);
return attrs;
} catch (error: any) {
- console.error('Error fetching automation attributes, using fallback:', error);
- setAttributes(fallbackAttributes);
- return fallbackAttributes;
+ console.error('Error fetching automation attributes:', error);
+ setAttributes([]);
+ return [];
}
}, []);
@@ -322,13 +264,24 @@ export function useAutomationOperations() {
setCreateError(null);
try {
- // Get mandateId from session storage
- const currentUserJson = sessionStorage.getItem('currentUser');
- if (currentUserJson) {
- const currentUser = JSON.parse(currentUserJson);
- if (currentUser.mandateId) {
- data.mandateId = currentUser.mandateId;
+ // Validate required fields - mandateId and featureInstanceId must be provided
+ if (!data.mandateId || !data.featureInstanceId) {
+ throw new Error('mandateId and featureInstanceId are required');
+ }
+
+ // Convert placeholders to ensure all values are strings
+ if (data.placeholders) {
+ const convertedPlaceholders: Record = {};
+ for (const [key, value] of Object.entries(data.placeholders)) {
+ if (value === null || value === undefined) {
+ convertedPlaceholders[key] = '';
+ } else if (typeof value === 'object') {
+ convertedPlaceholders[key] = JSON.stringify(value);
+ } else {
+ convertedPlaceholders[key] = String(value);
+ }
}
+ data.placeholders = convertedPlaceholders;
}
const newAutomation = await createAutomationApi(request, data);
diff --git a/src/hooks/useInstancePermissions.tsx b/src/hooks/useInstancePermissions.tsx
index c048d73..86ef345 100644
--- a/src/hooks/useInstancePermissions.tsx
+++ b/src/hooks/useInstancePermissions.tsx
@@ -127,6 +127,11 @@ export function useCanViewFeatureView(viewCode: string): boolean {
return false;
}
+ // Check for wildcard "_all" permission first (item=None in backend = all views)
+ if (instance.permissions.views["_all"]) {
+ return true;
+ }
+
return instance.permissions.views[viewCode] ?? false;
}
diff --git a/src/pages/workflows/AutomationsPage.tsx b/src/pages/workflows/AutomationsPage.tsx
index ee83a73..feacdc1 100644
--- a/src/pages/workflows/AutomationsPage.tsx
+++ b/src/pages/workflows/AutomationsPage.tsx
@@ -9,9 +9,10 @@ import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'
import { useAutomations, useAutomationOperations, AutomationTemplate, Automation } from '../../hooks/useAutomations';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
-import { FaSync, FaRobot, FaPlay, FaPlus, FaToggleOn, FaToggleOff, FaFileAlt, FaStop, FaList, FaTimes, FaCheck, FaExclamationCircle, FaSpinner } from 'react-icons/fa';
+import { FaSync, FaRobot, FaRocket, FaPlus, FaPauseCircle, FaPlayCircle, FaFileAlt, FaStop, FaList, FaTimes, FaCheck, FaExclamationCircle, FaSpinner } from 'react-icons/fa';
import { useToast } from '../../contexts/ToastContext';
import { useApiRequest } from '../../hooks/useApi';
+import { useFeatureStore } from '../../stores/featureStore';
import styles from '../admin/Admin.module.css';
@@ -25,6 +26,15 @@ interface WorkflowLog {
}
export const AutomationsPage: React.FC = () => {
+ // Get mandate and feature instance from store (first chatbot instance or first available)
+ const { getAllInstances } = useFeatureStore();
+ const instances = getAllInstances();
+
+ // Find first chatbot instance, or fall back to first available instance
+ const chatbotInstance = instances.find(i => i.featureCode === 'chatbot') || instances[0];
+ const mandateId = chatbotInstance?.mandateId;
+ const featureInstanceId = chatbotInstance?.id;
+
// Data hook
const {
data: automations,
@@ -147,11 +157,24 @@ export const AutomationsPage: React.FC = () => {
// Handle create submit
const handleCreateSubmit = async (data: Partial) => {
- const result = await handleAutomationCreate(data as any);
+ // Validate context - mandateId and featureInstanceId are required
+ if (!mandateId || !featureInstanceId) {
+ showError('Fehler: Kein aktiver Mandant oder Feature-Instanz gefunden');
+ return;
+ }
+
+ // Add required fields from context
+ const createData = {
+ ...data,
+ mandateId: mandateId,
+ featureInstanceId: featureInstanceId,
+ };
+
+ const result = await handleAutomationCreate(createData as any);
if (result) {
setShowCreateModal(false);
showSuccess('Automatisierung erstellt');
- refetch();
+ await refetch();
}
};
@@ -162,7 +185,7 @@ export const AutomationsPage: React.FC = () => {
if (success) {
setEditingAutomation(null);
showSuccess('Automatisierung aktualisiert');
- refetch();
+ await refetch();
}
};
@@ -172,7 +195,7 @@ export const AutomationsPage: React.FC = () => {
const success = await handleAutomationDelete(automation.id);
if (success) {
showSuccess('Automatisierung gelöscht');
- refetch();
+ await refetch();
}
}
};
@@ -199,11 +222,35 @@ export const AutomationsPage: React.FC = () => {
const handleTemplateSelect = async (template: AutomationTemplate) => {
setShowTemplateModal(false);
- // Pre-fill form with template data
+ // Validate context - mandateId and featureInstanceId are required
+ if (!mandateId || !featureInstanceId) {
+ showError('Fehler: Kein aktiver Mandant oder Feature-Instanz gefunden');
+ return;
+ }
+
+ // Convert placeholder values to strings (backend expects Dict[str, str])
+ // Arrays and objects are converted to JSON strings
+ const convertedPlaceholders: Record = {};
+ const templateParams = template.parameters || {};
+ for (const [key, value] of Object.entries(templateParams)) {
+ if (value === null || value === undefined) {
+ convertedPlaceholders[key] = '';
+ } else if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
+ // Convert complex structures to JSON strings
+ convertedPlaceholders[key] = JSON.stringify(value);
+ } else {
+ // Keep primitive values as strings
+ convertedPlaceholders[key] = String(value);
+ }
+ }
+
+ // Pre-fill form with template data including required fields
const prefillData: Partial = {
+ mandateId: mandateId,
+ featureInstanceId: featureInstanceId,
label: template.template?.overview || 'Neue Automatisierung',
template: JSON.stringify(template.template, null, 2),
- placeholders: template.parameters || {},
+ placeholders: convertedPlaceholders,
active: false,
schedule: '0 */4 * * *',
};
@@ -212,7 +259,7 @@ export const AutomationsPage: React.FC = () => {
const result = await handleAutomationCreate(prefillData as any);
if (result) {
showSuccess('Automatisierung aus Vorlage erstellt');
- refetch();
+ await refetch();
}
};
@@ -530,14 +577,14 @@ export const AutomationsPage: React.FC = () => {
customActions={[
{
id: 'execute',
- icon: ,
+ icon: ,
onClick: handleExecute,
title: 'Ausführen',
loading: (row: any) => executingAutomations.has(row.id),
},
{
id: 'toggleActive',
- icon: (row: any) => row.active ? : ,
+ icon: (row: any) => row.active ? : ,
onClick: handleToggleActive,
title: (row: any) => row.active ? 'Deaktivieren' : 'Aktivieren',
} as any,