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,
|
ActionContextProvider,
|
||||||
css,
|
css,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
|
useAPIClient,
|
||||||
useCompile,
|
useCompile,
|
||||||
useDocumentTitle,
|
useDocumentTitle,
|
||||||
useResourceActionContext,
|
useResourceActionContext,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { str2moment } from '@nocobase/utils/client';
|
import { str2moment } from '@nocobase/utils/client';
|
||||||
import { Breadcrumb, Tag } from 'antd';
|
import { Breadcrumb, Dropdown, Space, Tag } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { CanvasContent } from './CanvasContent';
|
import { CanvasContent } from './CanvasContent';
|
||||||
import { ExecutionStatusOptionsMap, JobStatusOptions } from './constants';
|
import { ExecutionStatusOptionsMap, JobStatusOptions, JobStatusOptionsMap } from './constants';
|
||||||
import { FlowContext, useFlowContext } from './FlowContext';
|
import { FlowContext, useFlowContext } from './FlowContext';
|
||||||
import { lang, NAMESPACE } from './locale';
|
import { lang, NAMESPACE } from './locale';
|
||||||
import { instructions } from './nodes';
|
import { instructions } from './nodes';
|
||||||
import useStyles from './style';
|
import useStyles from './style';
|
||||||
import { linkNodes } from './utils';
|
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 {
|
function attachJobs(nodes, jobs: any[] = []): void {
|
||||||
const nodesMap = new Map();
|
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() {
|
export function ExecutionCanvas() {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const { data, loading } = useResourceActionContext();
|
const { data, loading } = useResourceActionContext();
|
||||||
@ -150,7 +244,7 @@ export function ExecutionCanvas() {
|
|||||||
items={[
|
items={[
|
||||||
{ title: <Link to={`/admin/settings/workflow/workflows`}>{lang('Workflow')}</Link> },
|
{ title: <Link to={`/admin/settings/workflow/workflows`}>{lang('Workflow')}</Link> },
|
||||||
{ title: <Link to={`/admin/settings/workflow/workflows/${workflow.id}`}>{workflow.title}</Link> },
|
{ title: <Link to={`/admin/settings/workflow/workflows/${workflow.id}`}>{workflow.title}</Link> },
|
||||||
{ title: <strong>{`#${execution.id}`}</strong> },
|
{ title: <ExecutionsDropdown /> },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</header>
|
</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 request from './request';
|
||||||
import sql from './sql';
|
import sql from './sql';
|
||||||
import update from './update';
|
import update from './update';
|
||||||
|
import { StatusIcon } from '../components/StatusIcon';
|
||||||
|
|
||||||
export interface Instruction {
|
export interface Instruction {
|
||||||
title: string;
|
title: string;
|
||||||
@ -238,11 +239,10 @@ export function RemoveButton() {
|
|||||||
|
|
||||||
function InnerJobButton({ job, ...props }) {
|
function InnerJobButton({ job, ...props }) {
|
||||||
const { styles } = useStyles();
|
const { styles } = useStyles();
|
||||||
const { icon, color } = JobStatusOptionsMap[job.status];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
|
<Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
|
||||||
<Tag color={color}>{icon}</Tag>
|
<StatusIcon status={job.status} />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -280,7 +280,6 @@ export function JobButton() {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: jobs.map((job) => {
|
items: jobs.map((job) => {
|
||||||
const { icon, color } = JobStatusOptionsMap[job.status];
|
|
||||||
return {
|
return {
|
||||||
key: job.id,
|
key: job.id,
|
||||||
label: (
|
label: (
|
||||||
@ -296,9 +295,7 @@ export function JobButton() {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<span className={cx(styles.nodeJobButtonClass, 'inner')}>
|
<StatusIcon status={job.status} />
|
||||||
<Tag color={color}>{icon}</Tag>
|
|
||||||
</span>
|
|
||||||
<time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
<time>{str2moment(job.updatedAt).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||||
</div>
|
</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`
|
branchBlockClass: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -259,20 +282,6 @@ const useStyles = createStyles(({ css, token }) => {
|
|||||||
&[type='button'] {
|
&[type='button'] {
|
||||||
border: none;
|
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`
|
nodeHeaderClass: css`
|
||||||
|
Loading…
Reference in New Issue
Block a user