feat(plugin-workflow): add execution dropdown for quick navigating (#2404)

This commit is contained in:
Junyi 2023-08-06 09:17:48 +07:00 committed by GitHub
parent 34ec7388df
commit f992987187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 25 deletions

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>
),

View File

@ -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`