import { ActionContextProvider, css, SchemaComponent, useAPIClient, useCompile, useDocumentTitle, useResourceActionContext, } from '@nocobase/client'; import { str2moment } from '@nocobase/utils/client'; import { Breadcrumb, Dropdown, Space, Tag } from 'antd'; import React, { useCallback, useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { CanvasContent } from './CanvasContent'; import { ExecutionStatusOptionsMap, JobStatusOptions, JobStatusOptionsMap } from './constants'; import { FlowContext, useFlowContext } from './FlowContext'; import { lang, NAMESPACE } from './locale'; import { instructions } from './nodes'; import useStyles from './style'; import { linkNodes } from './utils'; import { DownOutlined } from '@ant-design/icons'; import { StatusIcon } from './components/StatusIcon'; import classnames from 'classnames'; function attachJobs(nodes, jobs: any[] = []): void { const nodesMap = new Map(); nodes.forEach((item) => { item.jobs = []; nodesMap.set(item.id, item); }); jobs.forEach((item) => { const node = nodesMap.get(item.nodeId); node.jobs.push(item); item.node = { id: node.id, title: node.title, type: node.type, }; }); nodes.forEach((item) => { item.jobs = item.jobs.sort((a, b) => a.id - b.id); }); } function JobModal() { const compile = useCompile(); const { viewJob: job, setViewJob } = useFlowContext(); const { styles } = useStyles(); const { node = {} } = job ?? {}; const instruction = instructions.get(node.type); return ( {compile(instruction?.title)} {node.title} #{node.id} ), properties: { status: { type: 'number', title: `{{t("Status", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'Select', enum: JobStatusOptions, 'x-read-pretty': true, }, updatedAt: { type: 'string', title: `{{t("Executed at", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'DatePicker', 'x-component-props': { showTime: true, }, 'x-read-pretty': true, }, result: { type: 'object', title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'Input.JSON', 'x-component-props': { className: styles.nodeJobResultClass, }, 'x-read-pretty': true, }, }, }, }, }} /> ); } function ExecutionsDropdown(props) { const { execution } = useFlowContext(); const apiClient = useAPIClient(); const navigate = useNavigate(); const { styles } = useStyles(); const [executionsBefore, setExecutionsBefore] = useState([]); const [executionsAfter, setExecutionsAfter] = useState([]); useEffect(() => { if (!execution) { return; } apiClient .resource('executions') .list({ filter: { key: execution.key, id: { $lt: execution.id, }, }, sort: '-createdAt', pageSize: 10, fields: ['id', 'status', 'createdAt'], }) .then(({ data }) => { setExecutionsBefore(data.data); }) .catch(() => {}); }, [execution]); useEffect(() => { if (!execution) { return; } apiClient .resource('executions') .list({ filter: { key: execution.key, id: { $gt: execution.id, }, }, sort: 'createdAt', pageSize: 10, fields: ['id', 'status', 'createdAt'], }) .then(({ data }) => { setExecutionsAfter(data.data.reverse()); }) .catch(() => {}); }, [execution]); const onClick = useCallback( ({ key }) => { if (key != execution.id) { navigate(`/admin/settings/workflow/executions/${key}`); } }, [execution], ); return execution ? ( { return { key: item.id, label: ( {`#${item.id}`} ), icon: , }; }), }} > {`#${execution.id}`} ) : null; } export function ExecutionCanvas() { const compile = useCompile(); const { data, loading } = useResourceActionContext(); const { setTitle } = useDocumentTitle(); const [viewJob, setViewJob] = useState(null); useEffect(() => { const { workflow } = data?.data ?? {}; setTitle?.(`${workflow?.title ? `${workflow.title} - ` : ''}${lang('Execution history')}`); }, [data?.data]); if (!data?.data) { if (loading) { return
{lang('Loading')}
; } else { return
{lang('Load failed')}
; } } const { jobs = [], workflow: { nodes = [], revisions = [], ...workflow } = {}, ...execution } = data?.data ?? {}; linkNodes(nodes); attachJobs(nodes, jobs); const entry = nodes.find((item) => !item.upstream); const statusOption = ExecutionStatusOptionsMap[execution.status]; return (
{lang('Workflow')} }, { title: {workflow.title} }, { title: }, ]} />
); }