Unverified Commit cab944cb authored by JPengCheng's avatar JPengCheng Committed by GitHub

[Feature][admin,web] Add import and export tasks json (#749)

parent c12cdbd3
......@@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
......@@ -228,5 +229,30 @@ public class TaskController {
public Result getTaskAPIAddress() {
return Result.succeed(taskService.getTaskAPIAddress(), "重启成功");
}
/**
* 导出json
*/
@GetMapping(value = "/exportJsonByTaskId")
public Result exportJsonByTaskId(@RequestParam Integer id) {
return Result.succeed(taskService.exportJsonByTaskId(id),"获取成功");
}
/**
* 导出json数组
*/
@PostMapping(value = "/exportJsonByTaskIds")
public Result exportJsonByTaskIds(@RequestBody JsonNode para) {
return Result.succeed(taskService.exportJsonByTaskIds(para),"获取成功");
}
/**
* json文件上传 导入task
*/
@PostMapping(value="/uploadTaskJson")
public Result uploadTaskJson(@RequestParam("file") MultipartFile file) throws Exception {
return taskService.uploadTaskJson(file);
}
}
......@@ -45,4 +45,15 @@ public class Catalogue extends SuperEntity {
private Integer parentId;
private Boolean isLeaf;
public Catalogue() {
}
public Catalogue(String name, Integer taskId, String type, Integer parentId, Boolean isLeaf) {
this.setName(name);
this.taskId = taskId;
this.type = type;
this.parentId = parentId;
this.isLeaf = isLeaf;
}
}
......@@ -27,7 +27,9 @@ import com.dlink.assertion.Asserts;
import com.dlink.db.model.SuperEntity;
import com.dlink.job.JobConfig;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
......@@ -104,6 +106,23 @@ public class Task extends SuperEntity {
@TableField(exist = false)
private List<Map<String, String>> config = new ArrayList<>();
@TableField(exist = false)
private String path;
@TableField(exist = false)
private String jarName;
@TableField(exist = false)
private String clusterConfigurationName;
@TableField(exist = false)
private String databaseName;
@TableField(exist = false)
private String envName;
@TableField(exist = false)
private String alertGroupName;
public List<Map<String, String>> parseConfig() {
ObjectMapper objectMapper = new ObjectMapper();
......@@ -135,4 +154,30 @@ public class Task extends SuperEntity {
alias, fg, sts, batchModel, checkPoint, parallelism, savePointStrategy, savePointPath, map);
}
public JsonNode parseJsonNode(){
ObjectMapper mapper = new ObjectMapper();
return parseJsonNode(mapper);
}
public JsonNode parseJsonNode(ObjectMapper mapper){
JsonNode jsonNode = mapper.createObjectNode();
((ObjectNode)jsonNode).put("name",this.getName());
((ObjectNode)jsonNode).put("alias",this.alias);
((ObjectNode)jsonNode).put("dialect",this.dialect);
((ObjectNode)jsonNode).put("type",this.type);
((ObjectNode)jsonNode).put("statement",this.statement);
((ObjectNode)jsonNode).put("checkPoint",this.checkPoint);
((ObjectNode)jsonNode).put("savePointStrategy",this.savePointStrategy);
((ObjectNode)jsonNode).put("savePointPath",this.savePointPath);
((ObjectNode)jsonNode).put("parallelism",this.parallelism);
((ObjectNode)jsonNode).put("fragment",this.fragment);
((ObjectNode)jsonNode).put("statementSet",this.statementSet);
((ObjectNode)jsonNode).put("batchModel",this.batchModel);
((ObjectNode)jsonNode).put("clusterName",this.clusterName);
((ObjectNode)jsonNode).put("configJson",this.configJson);
((ObjectNode)jsonNode).put("note",this.note);
((ObjectNode)jsonNode).put("step",this.step);
((ObjectNode)jsonNode).put("enabled",this.getEnabled());
return jsonNode;
}
}
......@@ -49,4 +49,6 @@ public interface CatalogueService extends ISuperService<Catalogue> {
boolean moveCatalogue(Integer id, Integer parentId);
boolean copyTask(Catalogue catalogue);
Integer addDependCatalogue(String[] catalogueNames);
}
......@@ -29,6 +29,8 @@ import com.dlink.model.JobInfoDetail;
import com.dlink.model.JobInstance;
import com.dlink.model.Task;
import com.dlink.result.SqlExplainResult;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
......@@ -85,4 +87,10 @@ public interface TaskService extends ISuperService<Task> {
Result rollbackTask(TaskRollbackVersionDTO dto);
Integer queryAllSizeByName(String name);
String exportJsonByTaskId(Integer taskId);
String exportJsonByTaskIds(JsonNode para);
Result uploadTaskJson(MultipartFile file) throws Exception;
}
......@@ -23,7 +23,9 @@ package com.dlink.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dlink.assertion.Asserts;
import com.dlink.db.service.impl.SuperServiceImpl;
import com.dlink.dto.CatalogueTaskDTO;
import com.dlink.mapper.CatalogueMapper;
......@@ -34,6 +36,7 @@ import com.dlink.model.Task;
import com.dlink.service.CatalogueService;
import com.dlink.service.StatementService;
import com.dlink.service.TaskService;
import org.apache.kafka.common.internals.Topic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
......@@ -192,4 +195,24 @@ public class CatalogueServiceImpl extends SuperServiceImpl<CatalogueMapper, Cata
return this.save(catalogue);
}
@Override
public Integer addDependCatalogue(String[] catalogueNames){
Integer parentId = 0;
for (int i = 0; i < catalogueNames.length - 1; i++) {
String catalogueName = catalogueNames[i];
Catalogue catalogue = getOne(new QueryWrapper<Catalogue>().eq("name", catalogueName).eq("parent_id", parentId).last(" limit 1"));
if (Asserts.isNotNull(catalogue)) {
parentId = catalogue.getId();
continue;
}
catalogue = new Catalogue();
catalogue.setName(catalogueName);
catalogue.setParentId(parentId);
catalogue.setIsLeaf(false);
this.save(catalogue);
parentId = catalogue.getId();
}
return parentId;
}
}
......@@ -208,6 +208,21 @@ export const handleOption = async (url:string,title:string,param:any) => {
}
};
export const handleData = async (url:string,id:any) => {
try {
const {code,datas,msg} = await getData(url,id);
if(code == CODE.SUCCESS){
return datas;
}else{
message.warn(msg);
return false;
}
} catch (error) {
message.error('获取失败,请重试');
return false;
}
};
export const handleInfo = async (url:string,id:number) => {
try {
const {datas} = await getInfoById(url,id);
......
......@@ -20,14 +20,21 @@
import React, {useEffect, useState, Key} from "react";
import {connect} from "umi";
import {DownOutlined, SwitcherOutlined, FolderAddOutlined} from "@ant-design/icons";
import {Tree, Menu, Empty, Button, message, Modal, Tooltip, Row, Col, Input} from 'antd';
import {DownOutlined, SwitcherOutlined, FolderAddOutlined, UploadOutlined, DownloadOutlined} from "@ant-design/icons";
import {Tree, Menu, Empty, Button, message, Modal, Tooltip, Row, Col, Input, Upload} from 'antd';
import type { UploadProps } from 'antd';
import {getCatalogueTreeData} from "@/pages/DataStudio/service";
import {convertToTreeData, getTreeNodeByKey, TreeDataNode} from "@/components/Studio/StudioTree/Function";
import style from "./index.less";
import {StateType} from "@/pages/DataStudio/model";
import {
getInfoById, handleAddOrUpdate, handleAddOrUpdateWithResult, handleOption, handleRemoveById, handleSubmit
handleData,
getInfoById,
handleAddOrUpdate,
handleAddOrUpdateWithResult,
handleOption,
handleRemoveById,
handleSubmit, CODE, postAll,
} from "@/components/Common/crud";
import UpdateCatalogueForm from './components/UpdateCatalogueForm';
import SimpleTaskForm from "@/components/Studio/StudioTree/components/SimpleTaskForm";
......@@ -104,6 +111,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
const [searchValue, setSearchValue] = useState('');
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [cutId, setCutId] = useState<number | undefined>(undefined);
const [exportTaskIds, setExportTaskIds] = useState<any[]>([]);
const getTreeData = async () => {
const result = await getCatalogueTreeData();
......@@ -123,6 +131,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
//默认展开所有
setExpandedKeys(expendList || []);
setDefaultExpandedKeys(expendList || []);
setExportTaskIds([]);
};
const onChange = (e: any) => {
......@@ -192,6 +201,8 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
toPaste(rightClickNode);
} else if (key == 'Copy') {
toCopy(rightClickNode);
}else if (key == 'ExportJson') {
toExportJson(rightClickNode);
}
};
......@@ -340,6 +351,54 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
}
};
const toExportJson = async (node: TreeDataNode | undefined) => {
let taskId = node?.taskId;
const datas = await handleData('/api/task/exportJsonByTaskId',{id:taskId});
if (datas) {
let data = JSON.parse(datas);
saveJSON(data,data.alias);
message.success('导出json成功');
}
};
const toExportSelectedTaskJson = async () => {
if (exportTaskIds.length <= 0) {
message.warn("请先选择要导出的作业");
} else {
try {
const {code, datas, msg} = await postAll('/api/task/exportJsonByTaskIds', {taskIds:exportTaskIds});
if (code == CODE.SUCCESS) {
saveJSON(datas);
message.success('导出json成功');
} else {
message.warn(msg);
}
} catch (error) {
message.error('获取失败,请重试');
}
}
}
const saveJSON = (data:any, filename?:any) => {
if (!data) {
message.error("保存的json数据为空");
return;
}
if (!filename)
filename = new Date().toLocaleDateString().replaceAll("/", "-");
if (typeof data === 'object') {
data = JSON.stringify(data, undefined, 4)
}
let blob = new Blob([data], {type: 'text/json'}),
e = document.createEvent('MouseEvents'),
a = document.createElement('a')
a.download = filename + '.json'
a.href = window.URL.createObjectURL(blob)
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
a.dispatchEvent(e)
}
const createTask = (node: TreeDataNode | undefined) => {
if (!node?.isLeaf) {
handleUpdateTaskModalVisible(true);
......@@ -384,6 +443,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
menuItems = (<>
<Menu.Item key='Open'>{'打开'}</Menu.Item>
<Menu.Item key='Submit'>{'异步提交'}</Menu.Item>
<Menu.Item key='ExportJson'>{'导出Json'}</Menu.Item>
<Menu.Item key='Rename'>{'重命名'}</Menu.Item>
<Menu.Item key='Copy'>{'复制'}</Menu.Item>
<Menu.Item key='Cut'>{'剪切'}</Menu.Item>
......@@ -462,6 +522,13 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
});
toOpen(e.node);
}
let taskIds = [];
for (let i = 0; i < e.selectedNodes.length; i++) {
if(e.selectedNodes[i].isLeaf){
taskIds.push(e.selectedNodes[i].taskId);
}
}
setExportTaskIds(taskIds);
};
const offExpandAll = () => {
......@@ -517,6 +584,27 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
};
});
const uProps: UploadProps = {
name: 'file',
action: '/api/task/uploadTaskJson',
accept: 'application/json',
headers: {
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status === 'done') {
if(info.file.response.code == CODE.SUCCESS){
message.success(info.file.response.msg);
}else{
message.warn(info.file.response.msg);
}
getTreeData();
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
};
return (
<div className={style.tree_div}>
<Row>
......@@ -535,6 +623,21 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
onClick={offExpandAll}
/>
</Tooltip>
<Tooltip title="导出json">
<Button
type="text"
icon={<DownloadOutlined />}
onClick={toExportSelectedTaskJson}
/>
</Tooltip>
<Upload {...uProps}>
<Tooltip title="导入json">
<Button
type="text"
icon={<UploadOutlined/>}
/>
</Tooltip>
</Upload>
</Col>
</Row>
<Search style={{marginBottom: 8}} placeholder="Search" onChange={onChange} allowClear={true}/>
......
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