diff --git a/packages/plugins/workflow/src/client/ExecutionCanvas.tsx b/packages/plugins/workflow/src/client/ExecutionCanvas.tsx index 2b3fe78fc..9d35cf0d5 100644 --- a/packages/plugins/workflow/src/client/ExecutionCanvas.tsx +++ b/packages/plugins/workflow/src/client/ExecutionCanvas.tsx @@ -2,21 +2,25 @@ import { ActionContextProvider, css, SchemaComponent, + useAPIClient, useCompile, useDocumentTitle, useResourceActionContext, } from '@nocobase/client'; import { str2moment } from '@nocobase/utils/client'; -import { Breadcrumb, Tag } from 'antd'; -import React, { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +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 } from './constants'; +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(); @@ -107,6 +111,96 @@ function JobModal() { ); } +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(); @@ -150,7 +244,7 @@ export function ExecutionCanvas() { items={[ { title: {lang('Workflow')} }, { title: {workflow.title} }, - { title: {`#${execution.id}`} }, + { title: }, ]} /> diff --git a/packages/plugins/workflow/src/client/components/StatusIcon.tsx b/packages/plugins/workflow/src/client/components/StatusIcon.tsx new file mode 100644 index 000000000..17c67f82d --- /dev/null +++ b/packages/plugins/workflow/src/client/components/StatusIcon.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Tag } from 'antd'; +import classnames from 'classnames'; + +import { createStyles } from '@nocobase/client'; + +import { JobStatusOptionsMap } from '../constants'; + +const useStyles = createStyles(({ css, token }) => ({ + statusIconClass: css` + padding: 0; + width: ${token.sizeLG}px; + height: ${token.sizeLG}px; + line-height: ${token.sizeLG}px; + margin-right: 0; + border-radius: 50%; + text-align: center; + `, +})); + +export function StatusIcon(props) { + const { icon, color } = JobStatusOptionsMap[props.status]; + const { styles } = useStyles(); + + return ( + + {icon} + + ); +} diff --git a/packages/plugins/workflow/src/client/nodes/index.tsx b/packages/plugins/workflow/src/client/nodes/index.tsx index d02a33595..b1eb35033 100644 --- a/packages/plugins/workflow/src/client/nodes/index.tsx +++ b/packages/plugins/workflow/src/client/nodes/index.tsx @@ -36,6 +36,7 @@ import query from './query'; import request from './request'; import sql from './sql'; import update from './update'; +import { StatusIcon } from '../components/StatusIcon'; export interface Instruction { title: string; @@ -238,11 +239,10 @@ export function RemoveButton() { function InnerJobButton({ job, ...props }) { const { styles } = useStyles(); - const { icon, color } = JobStatusOptionsMap[job.status]; return ( ); } @@ -280,7 +280,6 @@ export function JobButton() { { - const { icon, color } = JobStatusOptionsMap[job.status]; return { key: job.id, label: ( @@ -296,9 +295,7 @@ export function JobButton() { } `} > - - {icon} - + ), diff --git a/packages/plugins/workflow/src/client/style.tsx b/packages/plugins/workflow/src/client/style.tsx index 26becd494..0f2a01964 100644 --- a/packages/plugins/workflow/src/client/style.tsx +++ b/packages/plugins/workflow/src/client/style.tsx @@ -85,6 +85,29 @@ const useStyles = createStyles(({ css, token }) => { } `, + executionsDropdownRowClass: css` + .row { + display: flex; + align-items: baseline; + + &.current { + font-weight: bold; + } + + .id { + flex-grow: 1; + text-align: right; + } + + time { + width: 12em; + margin-left: 0.5rem; + color: ${token.colorText}; + font-size: 80%; + } + } + `, + branchBlockClass: css` display: flex; position: relative; @@ -259,20 +282,6 @@ const useStyles = createStyles(({ css, token }) => { &[type='button'] { border: none; } - - &.inner { - position: static; - } - - .ant-tag { - padding: 0; - width: ${token.sizeLG}px; - height: ${token.sizeLG}px; - line-height: ${token.sizeLG}px; - margin-right: 0; - border-radius: 50%; - text-align: center; - } `, nodeHeaderClass: css`