feat(plugin-workflow): add execution dropdown for quick navigating (#2404)
This commit is contained in:
parent
34ec7388df
commit
f992987187
@ -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 ? (
|
||||
<Dropdown
|
||||
menu={{
|
||||
className: styles.executionsDropdownRowClass,
|
||||
onClick,
|
||||
items: [...executionsAfter, execution, ...executionsBefore].map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
label: (
|
||||
<span className={classnames('row', { current: item.id === execution.id })}>
|
||||
<span className="id">{`#${item.id}`}</span>
|
||||
<time>{str2moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||
</span>
|
||||
),
|
||||
icon: <StatusIcon status={item.status} />,
|
||||
};
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<strong>{`#${execution.id}`}</strong>
|
||||
<DownOutlined />
|
||||
</Space>
|
||||
</Dropdown>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export function ExecutionCanvas() {
|
||||
const compile = useCompile();
|
||||
const { data, loading } = useResourceActionContext();
|
||||
@ -150,7 +244,7 @@ export function ExecutionCanvas() {
|
||||
items={[
|
||||
{ title: <Link to={`/admin/settings/workflow/workflows`}>{lang('Workflow')}</Link> },
|
||||
{ title: <Link to={`/admin/settings/workflow/workflows/${workflow.id}`}>{workflow.title}</Link> },
|
||||
{ title: <strong>{`#${execution.id}`}</strong> },
|
||||
{ title: <ExecutionsDropdown /> },
|
||||
]}
|
||||
/>
|
||||
</header>
|
||||
|
@ -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 (
|
||||
<Tag color={color} className={classnames(styles.statusIconClass, props.className)}>
|
||||
{icon}
|
||||
</Tag>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
|
||||
<Tag color={color}>{icon}</Tag>
|
||||
<StatusIcon status={job.status} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@ -280,7 +280,6 @@ export function JobButton() {
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: jobs.map((job) => {
|
||||
const { icon, color } = JobStatusOptionsMap[job.status];
|
||||
return {
|
||||
key: job.id,
|
||||
label: (
|
||||
@ -296,9 +295,7 @@ export function JobButton() {
|
||||
}
|
||||
`}
|
||||
>
|
||||
<span className={cx(styles.nodeJobButtonClass, 'inner')}>
|
||||
<Tag color={color}>{icon}</Tag>
|
||||
</span>
|
||||
<StatusIcon status={job.status} />
|
||||
<time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||
</div>
|
||||
),
|
||||
|
@ -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`
|
||||
|
Loading…
Reference in New Issue
Block a user