Commit 8534a53b authored by chenshouchao's avatar chenshouchao

Merge branch 'feat-20220801' into 'release'

cn-Feat 20220801

See merge request !57
parents 9b33af5f 6507ea38
/*
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-13 09:56:57
* @LastEditors: 吴永生#A02208 yongsheng.wu@wholion.com
* @LastEditTime: 2022-07-10 13:47:53
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-15 17:10:49
* @FilePath: /bkunyun/src/api/api_manager.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { BACKEND_API_URI_PREFIX } from "./api_prefix";
import { APIOPTION } from './fileserver/raysyncApi'
const RESTAPI = {
API_USER_FETCH: `${BACKEND_API_URI_PREFIX}/accounts/current`, //获取账户信息
......@@ -31,6 +32,7 @@ const RESTAPI = {
API_WORKBENCH_ADD_TEMPLATE: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowspec`, //项目管理员-添加工作流模板-提交
API_FETCH_TEMPLATE_INFO: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowspec`, //点击使用模版查看模版详情
API_WORK_FLOW_JOB: `${BACKEND_API_URI_PREFIX}/cpp/workbench/workflowjob`, //点击任务列表查看任务详情
API_FLOW_OUTPUT_NUMBER: `${ APIOPTION() }/readstreamnum`, //任务列表查看流输出数据集数量
API_WORKBENCH_WORKFLOWJOB_LIST: `${BACKEND_API_URI_PREFIX}/cpp/workbench/project/workflowjob`, //查询工作流任务
API_WORKBENCH_DEL_WORKFLOWJOB: `${BACKEND_API_URI_PREFIX}/cpp/workflow/job/`, //删除工作流任务
API_WORKBENCH_CANCEL_WORKFLOWJOB: `${BACKEND_API_URI_PREFIX}/cpp/workflow/cancel`, //取消工作流
......
......@@ -221,6 +221,15 @@ const fetchWorkFlowJob = (params: { id: string }) => {
});
};
// 工作列表详情获取流输出数量
const fetchFlowOutputNumber = (params: any) => {
return request({
url: Api.API_FLOW_OUTPUT_NUMBER,
method: "get",
params
});
};
type submitWorkFlowParams = {
name: string;
projectId: string;
......@@ -332,5 +341,6 @@ export {
getOverviewInfo,
getTaskOverview,
getOperatorList,
saveBatchActor
saveBatchActor,
fetchFlowOutputNumber
};
......@@ -30,6 +30,7 @@ interface IMyPopconfirmProps {
showCancel?: boolean;
onCancel?: any;
onConfirm?: any;
loading?: boolean;
}
const MyPopconfirm = (props: IMyPopconfirmProps) => {
......@@ -42,6 +43,7 @@ const MyPopconfirm = (props: IMyPopconfirmProps) => {
showCancel = true,
onCancel,
onConfirm,
loading = false,
} = props;
const open = useMemo(() => {
......@@ -97,7 +99,12 @@ const MyPopconfirm = (props: IMyPopconfirmProps) => {
style={{ marginRight: "12px" }}
/>
)}
<MyButton text={okText} onClick={handleOk} />
<MyButton
text={okText}
onClick={handleOk}
loading={loading}
isLoadingButton={true}
/>
</Box>
</Popper>
);
......
......@@ -100,7 +100,8 @@ const CustomOperator = observer((props: IProps) => {
return (
(outItem.domType || "").toLowerCase() === "dataset" ||
(outItem.domType || "").toLowerCase() === "file" ||
(outItem.domType || "").toLowerCase() === "path"
(outItem.domType || "").toLowerCase() === "path" ||
(outItem.domType || "").toLowerCase() === "input"
);
})
) {
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-11 17:13:52
* @LastEditTime: 2022-08-15 17:18:49
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/index.tsx
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -16,7 +16,11 @@ import IconButton from "@mui/material/IconButton";
import MyButton from "@/components/mui/MyButton";
import useMyRequest from "@/hooks/useMyRequest";
import { fetchWorkFlowJob, getworkFlowTaskInfo } from "@/api/project_api";
import {
fetchWorkFlowJob,
getworkFlowTaskInfo,
fetchFlowOutputNumber,
} from "@/api/project_api";
import { IResponse } from "@/api/http";
import jobSue from "@/assets/project/jobSue.svg";
import jobStop from "@/assets/project/jobStop.svg";
......@@ -37,6 +41,9 @@ import SeeDataset from "../ProjectData/SeeDataset";
import { getToken, storageUnitFromB } from "@/utils/util";
import LogView from "./LogView"
import usePass from "@/hooks/usePass";
import { IFlowNodeTransmissionNum } from "./interface";
import { getConnectionArr, getDatasetName, getDatasetPath, getSameBatch } from "./utils";
import styles from "./index.module.css";
const stateMap = {
......@@ -68,7 +75,8 @@ const ProjectSubmitWork = observer(() => {
const [activeFlowIndex, setActiveFlowIndex] = useState<number>(0);
const [showOptions, setShowOptions] = useState<boolean>(false);
const [randerOutputs1, setRanderOutputs] = useState<Array<any>>([]);
const location: any = useLocation();
const locationInfo: any = useLocation()?.state;
// const locationInfo: any = location?.state;
const navigate = useNavigate();
const message = useMessage();
const isPass = usePass();
......@@ -94,6 +102,8 @@ const ProjectSubmitWork = observer(() => {
getOutouts(res.data.outputs);
getLogs(res.data);
setWorkFlowJobInfo(res.data);
const newWorkflowId = locationInfo?.taskId?.replaceAll('-', '')
getFlowNumber({projectId,workflowId: newWorkflowId,filetoken:fileToken, token: getToken()})
},
});
......@@ -108,12 +118,55 @@ const ProjectSubmitWork = observer(() => {
setLogs(logs)
}
/** 更新流节点传输数据 */
const updateFlowNodeEdgeLabel = useCallback((flowNodeInfo: IFlowNodeTransmissionNum[])=>{
const newTasks = workFlowJobInfo && workFlowJobInfo?.tasks?.length && workFlowJobInfo?.tasks.map(item=>{
if(getSameBatch(flowNodeInfo, item)){
const newEdges = item?.edges?.length && item.edges.map(edgeItem => {
const connectionArr = getConnectionArr(flowNodeInfo,edgeItem )
if(connectionArr?.length){
return {
...edgeItem,
label: String(connectionArr[0]?.value)
}
}else {
return edgeItem
}
})
return {
...item,
edges: newEdges || [],
}
} else {
return item
}
})
if(workFlowJobInfo) {
setWorkFlowJobInfo({
...workFlowJobInfo,
tasks: newTasks || []
})
}
},[workFlowJobInfo])
/** 获取流节点数据 */
const { run: getFlowNumber } = useMyRequest(fetchFlowOutputNumber, {
pollingInterval: 1000 * 20,
pollingWhenHidden: false,
onSuccess: (res: IResponse<IFlowNodeTransmissionNum[]>) => {
if(res?.data?.length){
updateFlowNodeEdgeLabel(res.data)
}
},
});
useEffect(() => {
const locationInfo: any = location?.state;
run({
id: locationInfo.taskId,
});
}, [location?.state, run]);
}, [locationInfo.taskId, run]);
const { run: getworkFlowTaskInfoRun } = useMyRequest(getworkFlowTaskInfo, {
onSuccess: (res) => {
......@@ -144,30 +197,6 @@ const ProjectSubmitWork = observer(() => {
// return path;
// };
// 根据outputs的路径获取数据集的路径
const getDatasetPath = (path: string) => {
let datasetPath = "";
const noProjectPath = path.slice(12);
const fileIndex = noProjectPath.indexOf("/.dataset");
if (fileIndex !== -1) {
datasetPath = noProjectPath.slice(0, fileIndex);
} else {
datasetPath = noProjectPath;
}
return datasetPath ? datasetPath : "/";
};
// 根据outputs的路径获取数据集的名称
const getDatasetName = (path: string) => {
let name = "";
let nameIndex = path.indexOf("/.dataset/") + 10;
const lastIndex = path.lastIndexOf("/");
if (nameIndex !== -1 && lastIndex !== -1) {
name = path.slice(nameIndex, lastIndex);
}
return name;
};
// 下载任务结果
const handleDownLoadOutput = (item: any) => {
// 是文件下载
......@@ -187,7 +216,6 @@ const ProjectSubmitWork = observer(() => {
/** 返回事件 */
const onBack = useCallback(() => {
const locationInfo: any = location?.state;
if (locationInfo.from === "projectOverview") {
navigate("/product/cadd/projectOverview");
} else {
......
/*
* @Author: 吴永生 15770852798@163.com
* @Date: 2022-08-15 14:44:24
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-15 14:46:02
* @FilePath: /bkunyun/src/views/Project/ProjectJobDetail/interface.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
export interface IFlowNodeTransmissionNum {
target: string,
value: number,
batchId: string,
source: string
}
\ No newline at end of file
/*
* @Author: 吴永生 15770852798@163.com
* @Date: 2022-08-15 15:03:10
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-15 15:43:16
* @FilePath: /bkunyun/src/views/Project/ProjectJobDetail/utils.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
import { IEdge, ITask } from "../ProjectSubmitWork/interface";
import { IFlowNodeTransmissionNum } from "./interface";
// 根据outputs的路径获取数据集的路径
export const getDatasetPath = (path: string) => {
let datasetPath = "";
const noProjectPath = path.slice(12);
const fileIndex = noProjectPath.indexOf("/.dataset");
if (fileIndex !== -1) {
datasetPath = noProjectPath.slice(0, fileIndex);
} else {
datasetPath = noProjectPath;
}
return datasetPath ? datasetPath : "/";
};
// 根据outputs的路径获取数据集的名称
export const getDatasetName = (path: string) => {
let name = "";
let nameIndex = path.indexOf("/.dataset/") + 10;
const lastIndex = path.lastIndexOf("/");
if (nameIndex !== -1 && lastIndex !== -1) {
name = path.slice(nameIndex, lastIndex);
}
return name;
};
/** 根据批节点 判断是否在同一个批 */
export const getSameBatch = (flowNodeInfo: IFlowNodeTransmissionNum[], task: ITask) => {
return flowNodeInfo.some(every=>{
return every.batchId === task.parentNode
})
}
/** 连接的数组 */
export const getConnectionArr = (flowNodeInfo: IFlowNodeTransmissionNum[], edge: IEdge) => {
return flowNodeInfo.filter(every => {
return every.source === edge?.source && every.target === edge?.target
})
}
......@@ -74,6 +74,8 @@ const ProjectSubmitWork = observer(() => {
(parameter.domType || "").toLowerCase() === "checkbox"
) {
value = parameter.defaultValue.split(",");
} else if ((parameter.domType || "").toLowerCase() === "input") {
value = String(parameter.defaultValue);
} else {
value = parameter.defaultValue;
}
......@@ -82,6 +84,13 @@ const ProjectSubmitWork = observer(() => {
(parameter.domType || "").toLowerCase() === "checkbox"
) {
value = [];
} else if ((parameter.domType || "").toLowerCase() === "input") {
value =
String(parameter.defaultValue) === "null"
? ""
: String(parameter.defaultValue);
} else if ((parameter.domType || "").toLowerCase() === "radio") {
value = false;
} else {
value = "";
}
......@@ -106,7 +115,7 @@ const ProjectSubmitWork = observer(() => {
},
});
const { run: submitWorkFlowRun } = useMyRequest(submitWorkFlow, {
const { run: submitWorkFlowRun, loading } = useMyRequest(submitWorkFlow, {
onSuccess: (res) => {
Message.success("提交成功");
goToWorkbench(true);
......@@ -185,9 +194,17 @@ const ProjectSubmitWork = observer(() => {
) {
value = `/${value}`;
}
promotedParameters[parameter.name] = {
[parameter.classTypeName]: value,
};
if (tack.type === "BATCH") {
promotedParameters[`${tack.id}_${parameter.name}`] = {
[parameter.classTypeName]: value,
};
} else {
promotedParameters[
`${tack.parentNode}_${tack.prefix}_${parameter.name}`
] = {
[parameter.classTypeName]: value,
};
}
});
});
submitWorkFlowRun({
......@@ -315,6 +332,7 @@ const ProjectSubmitWork = observer(() => {
anchorEl={anchorEl}
onCancel={handleCancel}
onConfirm={handleConfirm}
loading={loading}
/>
</div>
);
......
......@@ -2,7 +2,7 @@
* @Author: 吴永生#A02208 yongsheng.wu@wholion.com
* @Date: 2022-06-21 20:03:56
* @LastEditors: 吴永生 15770852798@163.com
* @LastEditTime: 2022-08-08 15:55:45
* @LastEditTime: 2022-08-15 15:57:29
* @FilePath: /bkunyun/src/views/Project/ProjectSubmitWork/interface.ts
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
......@@ -46,7 +46,8 @@ export interface ITask {
};
tags?: string[];
type: IType | string; // 算子类型(批算子、流算子)
parentNode?: string; //
parentNode?: string; // 父节点id
prefix?: string; // 批算子内部流算子的前缀,为了区分重复的流算子
parameters: Array<IParameter>; // 参数组
edges: Array<IEdge>;
isCheck?: boolean; // 表单校验是否通过
......@@ -116,7 +117,7 @@ export type IRenderTask = {
// 工作流详情
export interface ITaskInfo extends ITemplateConfig {
name: string; // 任务(工作流)名称
name?: string; // 任务(工作流)名称
outputPath: string; // 任务结果 输出文件路径
state: IState; // 任务状态
specTitle: string; // 源模板
......
......@@ -20,14 +20,16 @@ export const getCheckResult = (
helperText = "该选项是必填项";
}
}
if (parameter.validators.length > 0) {
parameter.validators.forEach((validator) => {
const reg = new RegExp(validator.regex);
if (!reg.test(value)) {
error = true;
helperText = validator.message;
}
});
if (parameter?.validators) {
if (parameter.validators.length > 0) {
parameter.validators.forEach((validator) => {
const reg = new RegExp(validator.regex);
if (!reg.test(value)) {
error = true;
helperText = validator.message;
}
});
}
}
return {
error,
......
......@@ -79,30 +79,31 @@ const ProjectMembers = observer(() => {
const [size, setSize] = useState(10);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [count, setCount] = useState(0);
const [loading, setLoading] = useState(false)
/** 简单弹窗 */
const [jobData, setJobData] = useState("");
const [openDialog, setOpenDialog] = useState(false);
const [dialogType, setDialogType] = useState("del");
// 获取作业列表
const { run: getWorkflowJobInfo } = useMyRequest(getWorkflowJobList, {
onSuccess: (result: any) => {
setJobList(result.data.content);
setCount(result.data.totalElements);
setLoading(false);
timer && clearTimeout(timer as number);
timer = null;
timer = setTimeout(() => {
getWorkflowJobInfo({
projectId: currentProjectStore.currentProjectInfo.id as string,
page: page,
size: size,
name: jobName,
state: currency === "ALL" ? "" : currency,
});
}, 60000);
},
});
const { run: getWorkflowJobInfo, loading } = useMyRequest(
getWorkflowJobList,
{
onSuccess: (result: any) => {
setJobList(result.data.content);
setCount(result.data.totalElements);
timer && clearTimeout(timer as number);
timer = null;
timer = setTimeout(() => {
getWorkflowJobInfo({
projectId: currentProjectStore.currentProjectInfo.id as string,
page: page,
size: size,
name: jobName,
state: currency === "ALL" ? "" : currency,
});
}, 60000);
},
}
);
useEffect(() => {
return () => {
......@@ -281,7 +282,7 @@ const ProjectMembers = observer(() => {
const rowClick = useCallback(
(id: string) => {
navigate(`/product/cadd/projectJobDetail`, {
state: { taskId: id, from: 'workbenchList' },
state: { taskId: id, from: "workbenchList" },
});
},
[navigate]
......@@ -359,7 +360,6 @@ const ProjectMembers = observer(() => {
name: jobName,
state: currency === "ALL" ? "" : currency,
});
setLoading(true)
}}
>
<img alt="" src={onload} />
......@@ -410,7 +410,9 @@ const ProjectMembers = observer(() => {
</Box>
<Box className={styles.tabBoxMiddle}>
<img alt="" src={jobCost} />
<div className={styles.tabBoxTime}>{item.jobCost.toFixed(2)}</div>
<div className={styles.tabBoxTime}>
{item.jobCost.toFixed(2)}
</div>
</Box>
<Box className={styles.tabBoxJobStatus}>
<img alt="" src={renderStatusIcon(item.state)} />
......@@ -427,7 +429,10 @@ const ProjectMembers = observer(() => {
/>
</Box>
<div
style={{ color: renderTextColor(item.state), margin: '0px' }}
style={{
color: renderTextColor(item.state),
margin: "0px",
}}
className={styles.tabBoxStatusText}
>
{item.completeNum + "/" + item.totalNum}
......@@ -437,8 +442,14 @@ const ProjectMembers = observer(() => {
{item.state === "RUNNING" &&
isPass("PROJECT_WORKBENCH_JOBS_STOP", "USER") && (
<Box className={styles.tabBoxJobOperate}>
{
currentProjectStore.currentProjectInfo.projectRole === "USER" && (item.creator !== JSON.parse(localStorage.getItem("userInfo") || "{}")?.name) ? "" : <img
{currentProjectStore.currentProjectInfo.projectRole ===
"USER" &&
item.creator !==
JSON.parse(localStorage.getItem("userInfo") || "{}")
?.name ? (
""
) : (
<img
alt=""
src={jobStop}
style={{ cursor: "pointer" }}
......@@ -450,7 +461,7 @@ const ProjectMembers = observer(() => {
setDialogType("stop");
}}
/>
}
)}
</Box>
)}
{item.state !== "RUNNING" &&
......
......@@ -152,11 +152,13 @@ const BatchOperatorFlow = (props: IProps) => {
backgroundColor: "rgba(19, 112, 255, 1)",
border: "none",
left: 12,
top: "-2px",
},
outStyle: {
backgroundColor: "rgba(19, 112, 255, 1)",
border: "none",
left: 12,
bottom: "-2px",
},
},
......
......@@ -2,6 +2,7 @@
background-color: #f5f6f7;
border-radius: 2px;
padding: 6px 12px;
border: 1px solid #f5f6f7;
}
.flowNode:hover {
......@@ -24,22 +25,22 @@
margin-left: 8px;
}
.errorDot{
.errorDot {
display: inline-block;
line-height: 22px;
vertical-align: middle;
width: 8px;
height: 8px;
background-color: #FF4E4E;
background-color: #ff4e4e;
border-radius: 8px;
margin-left: 8px;
}
.handleBox::before{
.handleBox::before {
content: "";
position: absolute;
left: -4px;
top: -4px;
width: 14px;
height: 14px;
}
\ No newline at end of file
}
......@@ -37,7 +37,6 @@ const FlowNode = (props: any) => {
const { data } = props;
const {
dotStatus,
selectedStatus,
flowNodeStyle = { display: "flex", alignItems: "center" }, // 样式
inStyle = { background: "#C2C6CC ", left: 12 }, // 样式
......@@ -77,14 +76,6 @@ const FlowNode = (props: any) => {
[styles.selectedFlowBox]: selectedStatus,
})}
>
{/* {dotStatus?.isInput ? (
<Handle
className={styles.handleBox}
style={inStyle}
type="target"
position={Position.Top}
/>
) : null} */}
{inParamsArr?.length
? inParamsArr.map((item: any, index: number) => {
return (
......@@ -121,14 +112,6 @@ const FlowNode = (props: any) => {
/>
)}
</div>
{/* {dotStatus?.isOutput ? (
<Handle
className={styles.handleBox}
style={outStyle}
type="source"
position={Position.Bottom}
/>
) : null} */}
{outParamsArr?.length
? outParamsArr.map((item: any, index: number) => {
return (
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment