fix(plugin-workflow): fix status button styles (#2516)

This commit is contained in:
Junyi 2023-08-23 16:30:21 +07:00 committed by GitHub
parent 96caf9befa
commit 17f438ac39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 91 additions and 102 deletions

View File

@ -1,6 +1,6 @@
import { import {
ActionContextProvider, ActionContextProvider,
css, cx,
SchemaComponent, SchemaComponent,
useAPIClient, useAPIClient,
useCompile, useCompile,
@ -12,15 +12,14 @@ import { Breadcrumb, Dropdown, Space, Tag } from 'antd';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { CanvasContent } from './CanvasContent'; import { CanvasContent } from './CanvasContent';
import { ExecutionStatusOptionsMap, JobStatusOptions, JobStatusOptionsMap } from './constants'; import { ExecutionStatusOptionsMap, JobStatusOptions } 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 { DownOutlined } from '@ant-design/icons';
import { StatusIcon } from './components/StatusIcon'; import { StatusButton } from './components/StatusButton';
import classnames from 'classnames';
function attachJobs(nodes, jobs: any[] = []): void { function attachJobs(nodes, jobs: any[] = []): void {
const nodesMap = new Map(); const nodesMap = new Map();
@ -174,18 +173,23 @@ function ExecutionsDropdown(props) {
return execution ? ( return execution ? (
<Dropdown <Dropdown
menu={{ menu={{
className: styles.executionsDropdownRowClass,
onClick, onClick,
defaultSelectedKeys: [`${execution.id}`],
className: cx(styles.dropdownClass, styles.executionsDropdownRowClass),
items: [...executionsAfter, execution, ...executionsBefore].map((item) => { items: [...executionsAfter, execution, ...executionsBefore].map((item) => {
return { return {
key: item.id, key: item.id,
label: ( label: (
<span className={classnames('row', { current: item.id === execution.id })}> <>
<span className="id">{`#${item.id}`}</span> <span className="id">{`#${item.id}`}</span>
<time>{str2moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}</time> <time>{str2moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss')}</time>
</>
),
icon: (
<span>
<StatusButton statusMap={ExecutionStatusOptionsMap} status={item.status} />
</span> </span>
), ),
icon: <StatusIcon status={item.status} />,
}; };
}), }),
}} }}

View File

@ -159,7 +159,7 @@ export function WorkflowCanvas() {
menu={{ menu={{
onClick: onSwitchVersion, onClick: onSwitchVersion,
defaultSelectedKeys: [`${workflow.id}`], defaultSelectedKeys: [`${workflow.id}`],
className: cx(styles.workflowVersionDropdownClass), className: cx(styles.dropdownClass, styles.workflowVersionDropdownClass),
items: revisions items: revisions
.sort((a, b) => b.id - a.id) .sort((a, b) => b.id - a.id)
.map((item, index) => ({ .map((item, index) => ({

View File

@ -0,0 +1,42 @@
import React from 'react';
import { Button, Tag } from 'antd';
import classnames from 'classnames';
import { createStyles } from '@nocobase/client';
const useStyles = createStyles(({ css, token }) => ({
statusButtonClass: css`
border: none;
.ant-tag {
width: 100%;
height: 100%;
padding: 0;
margin-right: 0;
border-radius: 50%;
text-align: center;
}
`,
noStatusButtonClass: css`
border-width: 2px;
`,
}));
export function StatusButton(props) {
const { styles } = useStyles();
let tag = null;
if (typeof props.status !== 'undefined' && props.statusMap?.[props.status]) {
const { icon, color } = props.statusMap[props.status];
tag = <Tag color={color}>{icon}</Tag>;
}
return (
<Button
{...props}
shape="circle"
size="small"
className={classnames(tag ? styles.statusButtonClass : styles.noStatusButtonClass, props.className)}
>
{tag}
</Button>
);
}

View File

@ -1,30 +0,0 @@
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

@ -5,6 +5,8 @@ import {
CheckOutlined, CheckOutlined,
MinusOutlined, MinusOutlined,
ExclamationOutlined, ExclamationOutlined,
HourglassOutlined,
LoadingOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { NAMESPACE } from './locale'; import { NAMESPACE } from './locale';
@ -24,48 +26,56 @@ export const ExecutionStatusOptions = [
value: EXECUTION_STATUS.QUEUEING, value: EXECUTION_STATUS.QUEUEING,
label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`, label: `{{t("Queueing", { ns: "${NAMESPACE}" })}}`,
color: 'blue', color: 'blue',
icon: <HourglassOutlined />,
description: `{{t("Triggered but still waiting in queue to execute.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Triggered but still waiting in queue to execute.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.STARTED, value: EXECUTION_STATUS.STARTED,
label: `{{t("On going", { ns: "${NAMESPACE}" })}}`, label: `{{t("On going", { ns: "${NAMESPACE}" })}}`,
color: 'gold', color: 'gold',
icon: <LoadingOutlined />,
description: `{{t("Started and executing, maybe waiting for an async callback (manual, delay etc.).", { ns: "${NAMESPACE}" })}}`, description: `{{t("Started and executing, maybe waiting for an async callback (manual, delay etc.).", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.RESOLVED, value: EXECUTION_STATUS.RESOLVED,
label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`, label: `{{t("Resolved", { ns: "${NAMESPACE}" })}}`,
color: 'green', color: 'green',
icon: <CheckOutlined />,
description: `{{t("Successfully finished.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Successfully finished.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.FAILED, value: EXECUTION_STATUS.FAILED,
label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`, label: `{{t("Failed", { ns: "${NAMESPACE}" })}}`,
color: 'red', color: 'red',
icon: <ExclamationOutlined />,
description: `{{t("Failed to satisfy node configurations.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Failed to satisfy node configurations.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.ERROR, value: EXECUTION_STATUS.ERROR,
label: `{{t("Error", { ns: "${NAMESPACE}" })}}`, label: `{{t("Error", { ns: "${NAMESPACE}" })}}`,
color: 'red', color: 'red',
icon: <CloseOutlined />,
description: `{{t("Some node meets error.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Some node meets error.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.ABORTED, value: EXECUTION_STATUS.ABORTED,
label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`, label: `{{t("Aborted", { ns: "${NAMESPACE}" })}}`,
color: 'red', color: 'red',
icon: <MinusOutlined rotate={90} />,
description: `{{t("Running of some node was aborted by program flow.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Running of some node was aborted by program flow.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.CANCELED, value: EXECUTION_STATUS.CANCELED,
label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`, label: `{{t("Canceled", { ns: "${NAMESPACE}" })}}`,
color: 'volcano', color: 'volcano',
icon: <MinusOutlined rotate={45} />,
description: `{{t("Manually canceled whole execution when waiting.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Manually canceled whole execution when waiting.", { ns: "${NAMESPACE}" })}}`,
}, },
{ {
value: EXECUTION_STATUS.REJECTED, value: EXECUTION_STATUS.REJECTED,
label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`, label: `{{t("Rejected", { ns: "${NAMESPACE}" })}}`,
color: 'volcano', color: 'volcano',
icon: <MinusOutlined />,
description: `{{t("Rejected from a manual node.", { ns: "${NAMESPACE}" })}}`, description: `{{t("Rejected from a manual node.", { ns: "${NAMESPACE}" })}}`,
}, },
]; ];

View File

@ -36,7 +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'; import { StatusButton } from '../components/StatusButton';
export interface Instruction { export interface Instruction {
title: string; title: string;
@ -237,16 +237,6 @@ export function RemoveButton() {
); );
} }
function InnerJobButton({ job, ...props }) {
const { styles } = useStyles();
return (
<Button {...props} shape="circle" size="small" className={cx(styles.nodeJobButtonClass, props.className)}>
<StatusIcon status={job.status} />
</Button>
);
}
export function JobButton() { export function JobButton() {
const { execution, setViewJob } = useFlowContext(); const { execution, setViewJob } = useFlowContext();
const { jobs } = useNodeContext() ?? {}; const { jobs } = useNodeContext() ?? {};
@ -257,20 +247,7 @@ export function JobButton() {
} }
if (!jobs.length) { if (!jobs.length) {
return ( return <StatusButton className={styles.nodeJobButtonClass} disabled />;
<span
className={cx(
styles.nodeJobButtonClass,
css`
border: 2px solid #d9d9d9;
border-radius: 50%;
cursor: not-allowed;
width: 24px;
height: 24px;
`,
)}
/>
);
} }
function onOpenJob({ key }) { function onOpenJob({ key }) {
@ -285,31 +262,30 @@ export function JobButton() {
return { return {
key: job.id, key: job.id,
label: ( label: (
<div <>
className={css` <StatusButton statusMap={JobStatusOptionsMap} status={job.status} />
display: flex;
align-items: center;
gap: 0.5em;
time {
color: #999;
font-size: 0.8em;
}
`}
>
<StatusIcon status={job.status} />
<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> </>
), ),
}; };
}), }),
onClick: onOpenJob, onClick: onOpenJob,
className: styles.dropdownClass,
}} }}
> >
<InnerJobButton job={jobs[jobs.length - 1]} /> <StatusButton
statusMap={JobStatusOptionsMap}
status={jobs[jobs.length - 1].status}
className={styles.nodeJobButtonClass}
/>
</Dropdown> </Dropdown>
) : ( ) : (
<InnerJobButton job={jobs[0]} onClick={() => setViewJob(jobs[0])} /> <StatusButton
statusMap={JobStatusOptionsMap}
status={jobs[0].status}
onClick={() => setViewJob(jobs[0])}
className={styles.nodeJobButtonClass}
/>
); );
} }

View File

@ -15,12 +15,11 @@ const useStyles = createStyles(({ css, token }) => {
position: relative; position: relative;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background: ${token.colorBgContainer}; background: ${token.colorBgContainer};
border-bottom: 1px solid ${token.colorBorder}; border-bottom: 1px solid ${token.colorBorderSecondary};
header { header {
display: flex; display: flex;
align-items: center; align-items: center;
min-height: 2rem;
} }
aside { aside {
@ -55,7 +54,7 @@ const useStyles = createStyles(({ css, token }) => {
} }
`, `,
workflowVersionDropdownClass: css` dropdownClass: css`
.ant-dropdown-menu-item { .ant-dropdown-menu-item {
justify-content: flex-end; justify-content: flex-end;
.ant-dropdown-menu-title-content { .ant-dropdown-menu-title-content {
@ -65,11 +64,16 @@ const useStyles = createStyles(({ css, token }) => {
text-align: right; text-align: right;
time { time {
width: 12em; width: 14em;
color: ${token.colorText};
font-size: 80%; font-size: 80%;
} }
}
}
`,
workflowVersionDropdownClass: css`
.ant-dropdown-menu-item {
.ant-dropdown-menu-title-content {
strong { strong {
font-weight: normal; font-weight: normal;
} }
@ -90,24 +94,11 @@ const useStyles = createStyles(({ css, token }) => {
`, `,
executionsDropdownRowClass: css` executionsDropdownRowClass: css`
.row { .ant-dropdown-menu-item {
display: flex;
align-items: baseline;
&.current {
font-weight: bold;
}
.id { .id {
flex-grow: 1; flex-grow: 1;
text-align: right; text-align: right;
} }
time {
width: 12em;
color: ${token.colorText};
font-size: 80%;
}
} }
`, `,
@ -283,10 +274,6 @@ const useStyles = createStyles(({ css, token }) => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: ${token.colorTextLightSolid}; color: ${token.colorTextLightSolid};
&[type='button'] {
border: none;
}
`, `,
nodeHeaderClass: css` nodeHeaderClass: css`