automation fixautomation fixeses
Some checks failed
Deploy Nyla Frontend to Integration / deploy (push) Failing after 56s
Some checks failed
Deploy Nyla Frontend to Integration / deploy (push) Failing after 56s
This commit is contained in:
parent
7eb305f910
commit
6520763736
11 changed files with 112 additions and 36 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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[]
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
|
|
|
|||
|
|
@ -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 />,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue