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`