automation fixautomation fixeses
Some checks failed
Deploy Nyla Frontend to Integration / deploy (push) Failing after 56s

This commit is contained in:
ValueOn AG 2026-06-09 22:59:45 +02:00
parent 7eb305f910
commit 6520763736
11 changed files with 112 additions and 36 deletions

View file

@ -531,17 +531,18 @@ export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<an
/**
* Fetch node types for the flow builder (backend-driven).
* GET /api/workflow-automation/node-types?language=de
* GET /api/workflow-automation/node-types?mandateId=...&language=de
*/
export async function fetchNodeTypes(
request: ApiRequestFunction,
mandateId: string,
language = 'de'
): Promise<NodeTypesResponse> {
console.log(`${LOG} fetchNodeTypes: language=${language}`);
console.log(`${LOG} fetchNodeTypes: mandateId=${mandateId} language=${language}`);
const data = await request({
url: `${BASE}/node-types`,
method: 'get',
params: { language },
params: { mandateId, language },
});
const nodeTypes = data?.nodeTypes ?? [];
const categories = data?.categories ?? [];
@ -717,6 +718,7 @@ export async function fetchWorkflows(
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
});
if (data?.items && data?.pagination) return data;
if (data?.items) return data.items;
return data?.workflows ?? [];
}
@ -990,7 +992,7 @@ export async function fetchTasks(
method: 'get',
params,
});
return data?.tasks ?? [];
return data?.items ?? [];
}
export async function completeTask(

View file

@ -551,7 +551,7 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
setLoading(true);
setError(null);
try {
const data = await fetchNodeTypes(request, language);
const data = await fetchNodeTypes(request, mandateId || '', language);
setNodeTypes(data.nodeTypes);
setCategories(data.categories);
if (data.portTypeCatalog) {
@ -572,12 +572,12 @@ export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instance
const loadWorkflows = useCallback(async () => {
try {
const result = await fetchWorkflows(request);
const result = await fetchWorkflows(request, { mandateId: mandateId || undefined });
setWorkflows(Array.isArray(result) ? result : result.items);
} catch (e) {
console.error(`${LOG} loadWorkflows failed`, e);
}
}, [request]);
}, [request, mandateId]);
useEffect(() => {
loadNodeTypes();

View file

@ -100,9 +100,78 @@ export function fromApiGraph(
};
});
const hasPositions = nodes.length > 0 && nodes.some((n) => n.x !== 0 || n.y !== 0);
if (!hasPositions && nodes.length > 1) {
_autoLayoutTopDown(nodes, connections);
}
return { nodes, connections };
}
const _NODE_WIDTH = 200;
const _NODE_HEIGHT = 60;
const _VERTICAL_GAP = 80;
const _HORIZONTAL_GAP = 60;
function _autoLayoutTopDown(nodes: CanvasNode[], connections: CanvasConnection[]): void {
const inDegree = new Map<string, number>();
const children = new Map<string, string[]>();
for (const n of nodes) {
inDegree.set(n.id, 0);
children.set(n.id, []);
}
for (const c of connections) {
inDegree.set(c.targetId, (inDegree.get(c.targetId) ?? 0) + 1);
children.get(c.sourceId)?.push(c.targetId);
}
const layers: string[][] = [];
const assigned = new Set<string>();
const queue = nodes.filter((n) => (inDegree.get(n.id) ?? 0) === 0).map((n) => n.id);
while (queue.length > 0) {
const layer = [...queue];
layers.push(layer);
layer.forEach((id) => assigned.add(id));
const next: string[] = [];
for (const id of layer) {
for (const child of children.get(id) || []) {
if (assigned.has(child)) continue;
const remaining = connections.filter(
(c) => c.targetId === child && !assigned.has(c.sourceId)
);
if (remaining.length === 0) next.push(child);
}
}
queue.length = 0;
const unique = [...new Set(next)];
queue.push(...unique);
}
for (const n of nodes) {
if (!assigned.has(n.id)) {
layers.push([n.id]);
assigned.add(n.id);
}
}
const nodeById = new Map(nodes.map((n) => [n.id, n]));
let y = 40;
for (const layer of layers) {
const totalWidth = layer.length * _NODE_WIDTH + (layer.length - 1) * _HORIZONTAL_GAP;
let x = Math.max(40, (600 - totalWidth) / 2);
for (const id of layer) {
const node = nodeById.get(id);
if (node) {
node.x = x;
node.y = y;
x += _NODE_WIDTH + _HORIZONTAL_GAP;
}
}
y += _NODE_HEIGHT + _VERTICAL_GAP;
}
}
export function toApiGraph(
nodes: CanvasNode[],
connections: CanvasConnection[]

View file

@ -913,13 +913,14 @@ export function FormGeneratorTable<T extends Record<string, any>>({
return columnsRef.current;
}
// NO COLUMNS PROVIDED - this is an error in the calling component
// The calling component should provide columns from the /attributes/{entityType} endpoint
// Only warn when data is present but columns are still missing
if (data && data.length > 0) {
console.warn(
'⚠️ FormGeneratorTable: No columns provided! ' +
'Columns should come from Pydantic attribute definitions via /attributes/{entityType} endpoint. ' +
'Please ensure the calling component fetches and passes columns from the backend.'
);
}
// Return empty array - table will show no columns
return [];

View file

@ -25,7 +25,7 @@ import {
FaProjectDiagram, FaMapMarkedAlt, FaWallet, FaMoneyBillAlt, FaClock,
FaHeadset, FaVideo, FaHatWizard, FaStore, FaUserTie, FaClipboardList,
FaFileContract, FaGlobe, FaClipboardCheck,
FaSitemap, FaCopy, FaTasks,
FaSitemap,
} from 'react-icons/fa';
// =============================================================================
@ -58,11 +58,7 @@ export const PAGE_ICONS: Record<string, React.ReactNode> = {
'page.system.ragInventory': <FaDatabase />,
// System pages - Workflow Automation
'page.system.workflowAutomation.workflows': <FaSitemap />,
'page.system.workflowAutomation.editor': <FaProjectDiagram />,
'page.system.workflowAutomation.templates': <FaCopy />,
'page.system.workflowAutomation.runs': <FaPlay />,
'page.system.workflowAutomation.tasks': <FaTasks />,
'page.system.workflowAutomation': <FaSitemap />,
// Billing pages (legacy compat)
'page.billing.dashboard': <FaWallet />,

View file

@ -133,11 +133,11 @@ export function useUserFiles() {
} catch (error: any) {
console.error('Error fetching permissions:', error);
const defaultPerms: UserPermissions = {
view: false,
read: 'n',
create: 'n',
update: 'n',
delete: 'n',
view: true,
read: 'my',
create: 'my',
update: 'my',
delete: 'my',
};
setPermissions(defaultPerms);
return defaultPerms;

View file

@ -34,11 +34,12 @@ async function startWorkflowApi(
workflowData: StartWorkflowRequest,
options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' },
) {
const wfId = options?.workflowId ?? (workflowData as { workflowId?: string }).workflowId;
return await request({
url: `/api/workflow-automation/execute`,
method: 'post',
data: {
workflowId: options?.workflowId ?? (workflowData as { workflowId?: string }).workflowId,
workflowId: wfId,
payload: workflowData,
},
});

View file

@ -575,7 +575,7 @@ export const ComplianceAuditPage: React.FC = () => {
label: t('Datei'),
sortable: true,
width: 140,
formatter: (val: any) => (val ? `${String(val).slice(0, 8)}` : ''),
displayField: 'fileIdLabel',
},
], [t]);

View file

@ -40,6 +40,13 @@ export const WorkflowAutomationPage: React.FC = () => {
const [activeTab, setActiveTab] = useState<string>(initialRunId ? 'detail' : initialTab);
const [selectedRunId, setSelectedRunId] = useState<string | null>(initialRunId);
useEffect(() => {
const newTab = _TAB_ALIASES[searchParams.get('tab') || 'workflows'] || searchParams.get('tab') || 'workflows';
if (newTab !== activeTab) {
setActiveTab(newTab);
}
}, [searchParams]);
const [workflowFilter, setWorkflowFilter] = useState<string | null>(null);
const [selectedMandateId, setSelectedMandateId] = useState<string>('all');

View file

@ -338,7 +338,7 @@ export const _RunsTab: React.FC<RunsTabProps> = ({ workflowFilter, onRunClick, s
if (selectedMandateId !== 'all') params.mandateId = selectedMandateId;
const resp = await api.get('/api/workflow-automation/runs', { params });
const data = resp.data;
setRuns(data?.runs || []);
setRuns(data?.items || []);
const total = data?.total ?? 0;
const pageSize = pag.pageSize;
setPaginationMeta({
@ -416,7 +416,7 @@ export const _RunsTab: React.FC<RunsTabProps> = ({ workflowFilter, onRunClick, s
width: 140,
sortable: true,
filterable: true,
displayField: 'mandateLabel',
displayField: 'mandateIdLabel',
},
{
key: 'featureInstanceId',
@ -424,7 +424,7 @@ export const _RunsTab: React.FC<RunsTabProps> = ({ workflowFilter, onRunClick, s
width: 140,
sortable: true,
filterable: true,
displayField: 'instanceLabel',
displayField: 'featureInstanceIdLabel',
},
{
key: 'ownerId',
@ -432,7 +432,7 @@ export const _RunsTab: React.FC<RunsTabProps> = ({ workflowFilter, onRunClick, s
width: 140,
sortable: true,
filterable: true,
displayField: 'ownerLabel',
displayField: 'ownerIdLabel',
},
{ key: 'status', width: 110, sortable: true, filterable: true },
{

View file

@ -220,7 +220,7 @@ export const _WorkflowsTab: React.FC<WorkflowsTabProps> = ({ onWorkflowClick, se
width: 140,
sortable: true,
filterable: true,
displayField: 'mandateLabel',
displayField: 'mandateIdLabel',
},
{
key: 'featureInstanceId',
@ -228,15 +228,15 @@ export const _WorkflowsTab: React.FC<WorkflowsTabProps> = ({ onWorkflowClick, se
width: 140,
sortable: true,
filterable: true,
displayField: 'instanceLabel',
displayField: 'featureInstanceIdLabel',
},
{
key: 'ownerId',
label: t('Benutzer'),
key: 'sysCreatedBy',
label: t('Ersteller'),
width: 140,
sortable: true,
filterable: true,
displayField: 'ownerLabel',
displayField: 'sysCreatedByLabel',
},
{
key: 'active',