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,