Unverified Commit c6c2ed3b authored by aiwenmo's avatar aiwenmo Committed by GitHub

Fix v0.6.7-rc2 (#1030)

* [Fix][admin,web,app,core] Fix rc1 bugs (#1007)
Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>

* alter error (#1011)

* [Optimization-1014][client] Optimizate Doris sink and type convert and upgrade flink to 1.15.2 (#1015)
Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>

* UI page display misalignment fix (#1013)

Co-authored-by: steve <woai1998>

* out.collect在字段报错时没有上级抛错误,e.getCause().getMessage()会打不出错误栈,打印修改为logger.error("..",e). (#1016)

* Update cdcsource_statements.md (#1018)

cdcsource增加支持多目标库同步功能。

* Sqlserver fix 完善字段查询、字段类型判断功能 (#1021)

* Update cdcsource_statements.md

cdcsource增加支持多目标库同步功能。

* sqlserver优化字段属性类型解析.
sqlserver字段sql查询语句优化增加获取字段长度.

* sqlserver优化字段属性类型解析.
sqlserver字段sql查询语句优化增加获取字段长度.
Co-authored-by: 's avatar金鑫 <jinyanhui@huansi.net>

* [Fix-1020][core,web] Fix error in saving task after modifying task name (#1023)
Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>

* 修复sqlservercdc时decimal是base64的bug (#1026)

* Update cdcsource_statements.md

cdcsource增加支持多目标库同步功能。

* sqlserver优化字段属性类型解析.
sqlserver字段sql查询语句优化增加获取字段长度.
Co-authored-by: 's avatar金鑫 <jinyanhui@huansi.net>

* [Feature-1027][admin,web] Add data development task information log details button (#1029)
Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>
Co-authored-by: 's avatarwenmo <32723967+wenmo@users.noreply.github.com>
Co-authored-by: 's avatartoms <94617906+Toms1999@users.noreply.github.com>
Co-authored-by: 's avatarikiler <34333571+ikiler@users.noreply.github.com>
Co-authored-by: 's avatarmengyejiang <403905717@qq.com>
Co-authored-by: 's avatar金鑫 <jinxin@qdjinxin.net>
Co-authored-by: 's avatar金鑫 <jinyanhui@huansi.net>
parent 04b37e6e
...@@ -204,7 +204,7 @@ ...@@ -204,7 +204,7 @@
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}-${project.version}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</includes> </includes>
</fileSet> </fileSet>
<fileSet> <fileSet>
<directory>${project.parent.basedir}/dlink-admin/target/dlink-admin/lib</directory> <directory>${project.parent.basedir}/dlink-admin/target/dlink-admin-${project.version}/lib</directory>
<outputDirectory>lib</outputDirectory> <outputDirectory>lib</outputDirectory>
<includes> <includes>
<include>*.jar</include> <include>*.jar</include>
......
...@@ -56,8 +56,8 @@ public class SqlServerCDCBuilder extends AbstractCDCBuilder implements CDCBuilde ...@@ -56,8 +56,8 @@ public class SqlServerCDCBuilder extends AbstractCDCBuilder implements CDCBuilde
String database = config.getDatabase(); String database = config.getDatabase();
Properties debeziumProperties = new Properties(); Properties debeziumProperties = new Properties();
// 为部分转换添加默认值 // 为部分转换添加默认值
//debeziumProperties.setProperty("bigint.unsigned.handling.mode", "long"); debeziumProperties.setProperty("bigint.unsigned.handling.mode", "long");
//debeziumProperties.setProperty("decimal.handling.mode", "string"); debeziumProperties.setProperty("decimal.handling.mode", "string");
for (Map.Entry<String, String> entry : config.getDebezium().entrySet()) { for (Map.Entry<String, String> entry : config.getDebezium().entrySet()) {
if (Asserts.isNotNullString(entry.getKey()) && Asserts.isNotNullString(entry.getValue())) { if (Asserts.isNotNullString(entry.getKey()) && Asserts.isNotNullString(entry.getValue())) {
debeziumProperties.setProperty(entry.getKey(), entry.getValue()); debeziumProperties.setProperty(entry.getKey(), entry.getValue());
......
...@@ -49,19 +49,19 @@ public final class FlinkRestAPIConstant { ...@@ -49,19 +49,19 @@ public final class FlinkRestAPIConstant {
public static final String EXCEPTIONS = "/exceptions?maxExceptions=10"; public static final String EXCEPTIONS = "/exceptions?maxExceptions=10";
public static final String JOB_MANAGER = "/jobmanager/"; public static final String JOB_MANAGER = "/jobmanager";
public static final String TASK_MANAGER = "/taskmanagers/"; public static final String TASK_MANAGER = "/taskmanagers/";
public static final String METRICS = "/metrics/"; public static final String METRICS = "/metrics";
public static final String LOG = "/log/"; public static final String LOG = "/log";
public static final String LOGS = "/logs/"; public static final String LOGS = "/logs/";
public static final String STDOUT = "/stdout/"; public static final String STDOUT = "/stdout";
public static final String THREAD_DUMP = "/thread-dump/"; public static final String THREAD_DUMP = "/thread-dump";
public static final String GET = "?get="; public static final String GET = "?get=";
......
...@@ -33,16 +33,17 @@ public interface SqlServerConstant { ...@@ -33,16 +33,17 @@ public interface SqlServerConstant {
* 查询列信息模板SQL * 查询列信息模板SQL
*/ */
String QUERY_COLUMNS_SQL = " SELECT cast(a.name AS VARCHAR(500)) AS TABLE_NAME,cast(b.name AS VARCHAR(500)) AS COLUMN_NAME, isnull(CAST ( c.VALUE AS NVARCHAR ( 500 ) ),'') AS COMMENTS, " String QUERY_COLUMNS_SQL = " SELECT cast(a.name AS VARCHAR(500)) AS TABLE_NAME,cast(b.name AS VARCHAR(500)) AS COLUMN_NAME, isnull(CAST ( c.VALUE AS NVARCHAR ( 500 ) ),'') AS COMMENTS, "
+ " CASE b.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END as NULLVALUE,cast(sys.types.name AS VARCHAR (500)) AS DATA_TYPE," + " CASE b.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END as NULLVALUE,cast(sys.types.name AS VARCHAR (500)) AS DATA_TYPE,"
+ " ( SELECT CASE count(1) WHEN 1 then 'PRI' ELSE '' END FROM syscolumns,sysobjects,sysindexes,sysindexkeys,systypes WHERE syscolumns.xusertype = systypes.xusertype " + " ( SELECT CASE count(1) WHEN 1 then 'PRI' ELSE '' END FROM syscolumns,sysobjects,sysindexes,sysindexkeys,systypes WHERE syscolumns.xusertype = systypes.xusertype "
+ " AND syscolumns.id = object_id (a.name) AND sysobjects.xtype = 'PK' AND sysobjects.parent_obj = syscolumns.id " + " AND syscolumns.id = object_id (a.name) AND sysobjects.xtype = 'PK' AND sysobjects.parent_obj = syscolumns.id "
+ " AND sysindexes.id = syscolumns.id AND sysobjects.name = sysindexes.name AND sysindexkeys.id = syscolumns.id AND sysindexkeys.indid = sysindexes.indid " + " AND sysindexes.id = syscolumns.id AND sysobjects.name = sysindexes.name AND sysindexkeys.id = syscolumns.id AND sysindexkeys.indid = sysindexes.indid "
+ "AND syscolumns.colid = sysindexkeys.colid " + "AND syscolumns.colid = sysindexkeys.colid "
+ " AND syscolumns.name = b.name) as 'KEY', b.is_identity isIdentity , '' as CHARACTER_SET_NAME, '' as COLLATION_NAME, 0 as ORDINAL_POSITION, 0 as NUMERIC_PRECISION, 0 as NUMERIC_SCALE, " + " AND syscolumns.name = b.name) as 'KEY', b.is_identity isIdentity , '' as CHARACTER_SET_NAME, '' as COLLATION_NAME, "
+ "'' as AUTO_INCREMENT " + "0 as ORDINAL_POSITION, b.PRECISION as NUMERIC_PRECISION, b.scale as NUMERIC_SCALE,"
+ "FROM ( select name,object_id from sys.tables UNION all select name,object_id from sys.views ) a INNER JOIN sys.columns b " + "'' as AUTO_INCREMENT "
+ " ON b.object_id = a.object_id LEFT JOIN sys.types ON b.user_type_id = sys.types.user_type_id LEFT JOIN sys.extended_properties c ON c.major_id = b.object_id " + "FROM ( select name,object_id from sys.tables UNION all select name,object_id from sys.views ) a INNER JOIN sys.columns b "
+ "AND c.minor_id = b.column_id WHERE a.name = '%s' and sys.types.name !='sysname' "; + " ON b.object_id = a.object_id LEFT JOIN sys.types ON b.user_type_id = sys.types.user_type_id LEFT JOIN sys.extended_properties c ON c.major_id = b.object_id "
+ "AND c.minor_id = b.column_id WHERE a.name = '%s' and sys.types.name !='sysname' ";
/** /**
* 查询schema模板SQL * 查询schema模板SQL
......
...@@ -33,8 +33,8 @@ public class SqlServerTypeConvert implements ITypeConvert { ...@@ -33,8 +33,8 @@ public class SqlServerTypeConvert implements ITypeConvert {
String t = column.getType().toLowerCase(); String t = column.getType().toLowerCase();
boolean isNullable = !column.isKeyFlag() && column.isNullable(); boolean isNullable = !column.isKeyFlag() && column.isNullable();
if (t.contains("char") || t.contains("varchar") || t.contains("text") if (t.contains("char") || t.contains("varchar") || t.contains("text")
|| t.contains("nchar") || t.contains("nvarchar") || t.contains("ntext") || t.contains("nchar") || t.contains("nvarchar") || t.contains("ntext")
|| t.contains("uniqueidentifier") || t.contains("sql_variant")) { || t.contains("uniqueidentifier") || t.contains("sql_variant")) {
columnType = ColumnType.STRING; columnType = ColumnType.STRING;
} else if (t.contains("bigint")) { } else if (t.contains("bigint")) {
if (isNullable) { if (isNullable) {
...@@ -60,8 +60,7 @@ public class SqlServerTypeConvert implements ITypeConvert { ...@@ -60,8 +60,7 @@ public class SqlServerTypeConvert implements ITypeConvert {
} else { } else {
columnType = ColumnType.DOUBLE; columnType = ColumnType.DOUBLE;
} }
} else if (t.contains("decimal") || t.contains("money") || t.contains("smallmoney") } else if (t.contains("decimal") || t.contains("money") || t.contains("smallmoney") || t.contains("numeric")) {
|| t.contains("numeric")) {
columnType = ColumnType.DECIMAL; columnType = ColumnType.DECIMAL;
} else if (t.contains("real")) { } else if (t.contains("real")) {
if (isNullable) { if (isNullable) {
...@@ -69,10 +68,18 @@ public class SqlServerTypeConvert implements ITypeConvert { ...@@ -69,10 +68,18 @@ public class SqlServerTypeConvert implements ITypeConvert {
} else { } else {
columnType = ColumnType.FLOAT; columnType = ColumnType.FLOAT;
} }
} else if (t.contains("date")) { } else if (t.equalsIgnoreCase("datetime") || t.equalsIgnoreCase("smalldatetime")) {
columnType = ColumnType.DATE;
} else if (t.contains("smalldatetime") || t.contains("datetime")) {
columnType = ColumnType.TIMESTAMP; columnType = ColumnType.TIMESTAMP;
} else if (t.equalsIgnoreCase("datetime2")) {
//这里应该是纳秒
columnType = ColumnType.TIMESTAMP;
} else if (t.equalsIgnoreCase("datetimeoffset")) {
//这里应该是纳秒
columnType = ColumnType.TIMESTAMP;
} else if (t.equalsIgnoreCase("date")) {
columnType = ColumnType.LOCALDATE;
} else if (t.equalsIgnoreCase("time")) {
columnType = ColumnType.LOCALTIME;
} else if (t.contains("timestamp") || t.contains("binary") || t.contains("varbinary") || t.contains("image")) { } else if (t.contains("timestamp") || t.contains("binary") || t.contains("varbinary") || t.contains("image")) {
columnType = ColumnType.BYTES; columnType = ColumnType.BYTES;
} }
......
...@@ -19,12 +19,11 @@ ...@@ -19,12 +19,11 @@
import MonacoEditor from "react-monaco-editor"; import MonacoEditor from "react-monaco-editor";
import * as _monaco from "monaco-editor";
export type CodeShowFormProps = { export type CodeShowFormProps = {
height?: string; height?: string;
width?: string; width?: string;
language: string; language?: string;
theme?: string; theme?: string;
options?: any; options?: any;
code: string; code: string;
...@@ -40,8 +39,8 @@ const CodeShow = (props: CodeShowFormProps) => { ...@@ -40,8 +39,8 @@ const CodeShow = (props: CodeShowFormProps) => {
options = { options = {
selectOnLineNumbers: true, selectOnLineNumbers: true,
renderSideBySide: false, renderSideBySide: false,
autoIndent:'None', autoIndent: 'None',
readOnly:true , readOnly: true,
}, },
code, code,
} = props; } = props;
......
...@@ -26,50 +26,51 @@ import {Button, Input, Space} from "antd"; ...@@ -26,50 +26,51 @@ import {Button, Input, Space} from "antd";
const DTable = (props: any) => { const DTable = (props: any) => {
const {dataSource,columns} = props; const {dataSource, columns, scroll} = props;
const [data,setData] = useState<[]>([]); const [data, setData] = useState<[]>([]);
const refreshData = async () =>{ const refreshData = async () => {
const msg = await getData(dataSource.url, dataSource.params); const msg = await getData(dataSource.url, dataSource.params);
setData(msg.datas); setData(msg.datas);
}; };
const buildColumn = () =>{ const buildColumn = () => {
const columnList: any=[]; const columnList: any = [];
columns.map((item) => { columns.map((item) => {
const openSorter = item.openSorter==null?true:item.openSorter; const openSorter = item.openSorter == null ? true : item.openSorter;
const isString = item.isString==null?true:item.isString; const isString = item.isString == null ? true : item.isString;
const openSearch = item.openSearch==null?'like':item.openSearch; const openSearch = item.openSearch == null ? 'like' : item.openSearch;
let column = { let column = {
title: item.title?item.title:item.field, title: item.title ? item.title : item.field,
dataIndex: item.dataIndex?item.dataIndex:item.field, dataIndex: item.dataIndex ? item.dataIndex : item.field,
key: item.dataIndex?item.dataIndex:item.field, key: item.dataIndex ? item.dataIndex : item.field,
}; };
if(openSorter){ if (openSorter) {
if(isString){ if (isString) {
column = { column = {
sorter: (a, b) => { sorter: (a, b) => {
const value1 = a[column.dataIndex]!=null?a[column.dataIndex].toString():''; const value1 = a[column.dataIndex] != null ? a[column.dataIndex].toString() : '';
const value2 = b[column.dataIndex]!=null?b[column.dataIndex].toString():''; const value2 = b[column.dataIndex] != null ? b[column.dataIndex].toString() : '';
return value1.localeCompare(value2); return value1.localeCompare(value2);
}, },
...column, ...column,
} }
}else{ } else {
column = { column = {
sorter: (a, b) => a[column.dataIndex] - b[column.dataIndex], sorter: (a, b) => a[column.dataIndex] - b[column.dataIndex],
...column, ...column,
} }
} }
} }
if(openSearch==='like'){ if (openSearch === 'like') {
column = {...column,...getColumnSearchProps(column.dataIndex),} column = {...column, ...getColumnSearchProps(column.dataIndex),}
}else if(openSearch==='dict'){ } else if (openSearch === 'dict') {
column = { column = {
onFilter: (value, record) => record[column.dataIndex] === value, onFilter: (value, record) => record[column.dataIndex] === value,
...column,} ...column,
}
} }
columnList.push({ columnList.push({
...column, ...column,
...@@ -80,7 +81,7 @@ const DTable = (props: any) => { ...@@ -80,7 +81,7 @@ const DTable = (props: any) => {
} }
useEffect(() => { useEffect(() => {
if(dataSource&&dataSource.url){ if (dataSource && dataSource.url) {
refreshData(); refreshData();
} }
}, [dataSource]); }, [dataSource]);
...@@ -89,7 +90,8 @@ const DTable = (props: any) => { ...@@ -89,7 +90,8 @@ const DTable = (props: any) => {
<ProTable <ProTable
columns={buildColumn()} columns={buildColumn()}
style={{width: '100%'}} style={{width: '100%'}}
dataSource={dataSource?(dataSource.url?data:dataSource):[]} scroll={scroll}
dataSource={dataSource ? (dataSource.url ? data : dataSource) : []}
rowKey="name" rowKey="name"
pagination={{ pagination={{
pageSize: 10, pageSize: 10,
......
...@@ -26,18 +26,15 @@ import ProList from '@ant-design/pro-list'; ...@@ -26,18 +26,15 @@ import ProList from '@ant-design/pro-list';
import {handleRemove, queryData} from "@/components/Common/crud"; import {handleRemove, queryData} from "@/components/Common/crud";
import ProDescriptions from '@ant-design/pro-descriptions'; import ProDescriptions from '@ant-design/pro-descriptions';
import React, {useState} from "react"; import React, {useState} from "react";
import {ModalForm,} from '@ant-design/pro-form';
import styles from "./index.less";
import {Scrollbars} from 'react-custom-scrollbars';
import StudioPreview from "../StudioPreview"; import StudioPreview from "../StudioPreview";
import {getJobData} from "@/pages/DataStudio/service"; import {getJobData} from "@/pages/DataStudio/service";
import {HistoryItem} from "@/components/Studio/StudioConsole/StudioHistory/data"; import {HistoryItem} from "@/components/Studio/StudioConsole/StudioHistory/data";
import CodeShow from "@/components/Common/CodeShow"; import CodeShow from "@/components/Common/CodeShow";
const { Title, Paragraph, Text, Link } = Typography; const {Title, Paragraph, Text, Link} = Typography;
type HistoryConfig={ type HistoryConfig = {
useSession: boolean; useSession: boolean;
session: string; session: string;
useRemote: boolean; useRemote: boolean;
...@@ -62,41 +59,44 @@ type HistoryConfig={ ...@@ -62,41 +59,44 @@ type HistoryConfig={
const url = '/api/history'; const url = '/api/history';
const StudioHistory = (props: any) => { const StudioHistory = (props: any) => {
const {current,refs,dispatch} = props; const {current, refs, dispatch} = props;
const [modalVisit, setModalVisit] = useState(false); const [modalVisit, setModalVisit] = useState(false);
const [row, setRow] = useState<HistoryItem>(); const [row, setRow] = useState<HistoryItem>();
const [config,setConfig] = useState<HistoryConfig>(); const [config, setConfig] = useState<HistoryConfig>();
const [type,setType] = useState<number>(); const [type, setType] = useState<number>();
const [result,setResult] = useState<{}>(); const [result, setResult] = useState<{}>();
const showDetail=(row:HistoryItem,type:number)=>{ const showDetail = (row: HistoryItem, type: number) => {
setRow(row); setRow(row);
setModalVisit(true); setModalVisit(true);
setType(type); setType(type);
setConfig(JSON.parse(row.configJson)); setConfig(JSON.parse(row.configJson));
if(type===3){ if (type === 3) {
// showJobData(row.jobId,dispatch) // showJobData(row.jobId,dispatch)
const res = getJobData(row.jobId); const res = getJobData(row.jobId);
res.then((resd)=>{ res.then((resd) => {
setResult(resd.datas); setResult(resd.datas);
}); });
} }
}; };
const removeHistory=(row:HistoryItem)=>{ const removeHistory = (row: HistoryItem) => {
Modal.confirm({ Modal.confirm({
title: '删除执行记录', title: '删除执行记录',
content: '确定删除该执行记录吗?', content: '确定删除该执行记录吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk:async () => { onOk: async () => {
await handleRemove(url,[row]); await handleRemove(url, [row]);
// refs.current?.reloadAndRest?.();
refs.history?.current?.reload(); refs.history?.current?.reload();
} }
}); });
}; };
const handleCancel = () => {
setModalVisit(false);
};
return ( return (
<> <>
<ProList<HistoryItem> <ProList<HistoryItem>
...@@ -111,7 +111,7 @@ const StudioHistory = (props: any) => { ...@@ -111,7 +111,7 @@ const StudioHistory = (props: any) => {
}} }}
rowKey="id" rowKey="id"
headerTitle="执行历史" headerTitle="执行历史"
request={(params, sorter, filter) => queryData(url,{...params, sorter:{id:'descend'}, filter})} request={(params, sorter, filter) => queryData(url, {...params, sorter: {id: 'descend'}, filter})}
pagination={{ pagination={{
pageSize: 5, pageSize: 5,
}} }}
...@@ -124,7 +124,7 @@ const StudioHistory = (props: any) => { ...@@ -124,7 +124,7 @@ const StudioHistory = (props: any) => {
return ( return (
<Space size={0}> <Space size={0}>
<Tag color="blue" key={row.jobId}> <Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId} <FireOutlined/> {row.jobId}
</Tag> </Tag>
</Space> </Space>
); );
...@@ -132,7 +132,7 @@ const StudioHistory = (props: any) => { ...@@ -132,7 +132,7 @@ const StudioHistory = (props: any) => {
}, },
description: { description: {
search: false, search: false,
render:(_, row)=>{ render: (_, row) => {
return (<Paragraph> return (<Paragraph>
<blockquote> <blockquote>
<Link href={`http://${row.jobManagerAddress}`} target="_blank"> <Link href={`http://${row.jobManagerAddress}`} target="_blank">
...@@ -148,37 +148,37 @@ const StudioHistory = (props: any) => { ...@@ -148,37 +148,37 @@ const StudioHistory = (props: any) => {
render: (_, row) => { render: (_, row) => {
return ( return (
<Space size={0}> <Space size={0}>
{row.jobName?( {row.jobName ? (
<Tag color="gray" key={row.jobName}> <Tag color="gray" key={row.jobName}>
{row.jobName} {row.jobName}
</Tag> </Tag>
):''} ) : ''}
{row.session?( {row.session ? (
<Tag color="orange" key={row.session}> <Tag color="orange" key={row.session}>
<MessageOutlined /> {row.session} <MessageOutlined/> {row.session}
</Tag> </Tag>
):''} ) : ''}
{row.clusterAlias?( {row.clusterAlias ? (
<Tag color="green" key={row.clusterAlias}> <Tag color="green" key={row.clusterAlias}>
<ClusterOutlined /> {row.clusterAlias} <ClusterOutlined/> {row.clusterAlias}
</Tag> </Tag>
):(<Tag color="green" key={row.clusterAlias}> ) : (<Tag color="green" key={row.clusterAlias}>
<ClusterOutlined /> 本地环境 <ClusterOutlined/> 本地环境
</Tag>)} </Tag>)}
{row.type?( {row.type ? (
<Tag color="blue" key={row.type}> <Tag color="blue" key={row.type}>
<RocketOutlined /> {row.type} <RocketOutlined/> {row.type}
</Tag> </Tag>
):''} ) : ''}
{(row.status==2) ? {(row.status == 2) ?
(<><Badge status="success"/><Text type="success">SUCCESS</Text></>): (<><Badge status="success"/><Text type="success">SUCCESS</Text></>) :
(row.status==1) ? (row.status == 1) ?
<><Badge status="success"/><Text type="secondary">RUNNING</Text></> : <><Badge status="success"/><Text type="secondary">RUNNING</Text></> :
(row.status==3) ? (row.status == 3) ?
<><Badge status="error"/><Text type="danger">FAILED</Text></> : <><Badge status="error"/><Text type="danger">FAILED</Text></> :
(row.status==4) ? (row.status == 4) ?
<><Badge status="error"/><Text type="warning">CANCEL</Text></> : <><Badge status="error"/><Text type="warning">CANCEL</Text></> :
(row.status==0) ? (row.status == 0) ?
<><Badge status="error"/><Text type="warning">INITIALIZE</Text></> : <><Badge status="error"/><Text type="warning">INITIALIZE</Text></> :
<><Badge status="success"/><Text type="danger">UNKNOWEN</Text></>} <><Badge status="success"/><Text type="danger">UNKNOWEN</Text></>}
</Space> </Space>
...@@ -188,19 +188,29 @@ const StudioHistory = (props: any) => { ...@@ -188,19 +188,29 @@ const StudioHistory = (props: any) => {
}, },
actions: { actions: {
render: (text, row) => [ render: (text, row) => [
<a key="config" onClick={()=>{showDetail(row,1)}}> <a key="config" onClick={() => {
showDetail(row, 1)
}}>
执行配置 执行配置
</a>, </a>,
<a key="statement" onClick={()=>{showDetail(row,2)}}> <a key="statement" onClick={() => {
showDetail(row, 2)
}}>
FlinkSql语句 FlinkSql语句
</a>, </a>,
<a key="result" onClick={()=>{showDetail(row,3)}}> <a key="result" onClick={() => {
showDetail(row, 3)
}}>
预览数据 预览数据
</a>, </a>,
<a key="error" onClick={()=>{showDetail(row,4)}}> <a key="error" onClick={() => {
showDetail(row, 4)
}}>
异常信息 异常信息
</a>, </a>,
<a key="delete" onClick={()=>{removeHistory(row)}}> <a key="delete" onClick={() => {
removeHistory(row)
}}>
删除 删除
</a>, </a>,
], ],
...@@ -259,40 +269,35 @@ const StudioHistory = (props: any) => { ...@@ -259,40 +269,35 @@ const StudioHistory = (props: any) => {
}} }}
options={{ options={{
search: false, search: false,
setting:false setting: false
}} }}
/> />
<ModalForm <Modal
width={'80%'}
visible={modalVisit} visible={modalVisit}
onFinish={async () => { destroyOnClose
}} centered
onVisibleChange={setModalVisit} footer={false}
submitter={{ onCancel={handleCancel}
submitButtonProps: {
style: {
display: 'none',
},
},
}}
> >
{type==1 && ( {type == 1 && (
<ProDescriptions <ProDescriptions
column={2} column={2}
title='执行配置' title='执行配置'
> >
<ProDescriptions.Item span={2} label="JobId" > <ProDescriptions.Item span={2} label="JobId">
<Tag color="blue" key={row.jobId}> <Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId} <FireOutlined/> {row.jobId}
</Tag> </Tag>
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="共享会话" > <ProDescriptions.Item label="共享会话">
{config.useSession?'启用':'禁用'} {config.useSession ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="会话 Key"> <ProDescriptions.Item label="会话 Key">
{config.session} {config.session}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="执行方式" > <ProDescriptions.Item label="执行方式">
{config.useRemote?'远程':'本地'} {config.useRemote ? '远程' : '本地'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="任务类型"> <ProDescriptions.Item label="任务类型">
{config.type} {config.type}
...@@ -303,17 +308,17 @@ const StudioHistory = (props: any) => { ...@@ -303,17 +308,17 @@ const StudioHistory = (props: any) => {
<ProDescriptions.Item label="集群配置ID"> <ProDescriptions.Item label="集群配置ID">
{config.clusterConfigurationId} {config.clusterConfigurationId}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="预览结果" > <ProDescriptions.Item label="预览结果">
{config.useResult?'启用':'禁用'} {config.useResult ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="打印流" > <ProDescriptions.Item label="打印流">
{config.useChangeLog?'启用':'禁用'} {config.useChangeLog ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="最大行数"> <ProDescriptions.Item label="最大行数">
{config.maxRowNum} {config.maxRowNum}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="自动停止" > <ProDescriptions.Item label="自动停止">
{config.useAutoCancel?'启用':'禁用'} {config.useAutoCancel ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item span={2} label="JobManagerAddress"> <ProDescriptions.Item span={2} label="JobManagerAddress">
{row.jobManagerAddress} {row.jobManagerAddress}
...@@ -325,10 +330,10 @@ const StudioHistory = (props: any) => { ...@@ -325,10 +330,10 @@ const StudioHistory = (props: any) => {
{config.jobName} {config.jobName}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="片段机制"> <ProDescriptions.Item label="片段机制">
{config.useSqlFragment?'启用':'禁用'} {config.useSqlFragment ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="语句集"> <ProDescriptions.Item label="语句集">
{config.useStatementSet?'启用':'禁用'} {config.useStatementSet ? '启用' : '禁用'}
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item label="并行度"> <ProDescriptions.Item label="并行度">
{config.parallelism} {config.parallelism}
...@@ -344,59 +349,57 @@ const StudioHistory = (props: any) => { ...@@ -344,59 +349,57 @@ const StudioHistory = (props: any) => {
</ProDescriptions.Item> </ProDescriptions.Item>
</ProDescriptions> </ProDescriptions>
)} )}
{type==2 && ( {type == 2 && (
<ProDescriptions <ProDescriptions
column={1} column={1}
title='FlinkSql 语句' title='FlinkSql 语句'
> >
<ProDescriptions.Item label="JobId" > <ProDescriptions.Item label="JobId">
<Tag color="blue" key={row.jobId}> <Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId} <FireOutlined/> {row.jobId}
</Tag> </Tag>
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item> <ProDescriptions.Item>
<CodeShow width={"100%"} height={"500px"} language={"sql"} code={row.statement} theme={"vs-dark"}/> <CodeShow height={"80vh"} language={"sql"} code={row.statement} theme={"vs-dark"}/>
</ProDescriptions.Item> </ProDescriptions.Item>
</ProDescriptions> </ProDescriptions>
)} )}
{type==3 && ( {type == 3 && (
<ProDescriptions <ProDescriptions
column={2} column={2}
title='数据预览' title='数据预览'
> >
<ProDescriptions.Item span={2} label="JobId" > <ProDescriptions.Item span={2} label="JobId">
<Tag color="blue" key={row.jobId}> <Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId} <FireOutlined/> {row.jobId}
</Tag> </Tag>
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item span={2} > <ProDescriptions.Item span={2}>
<StudioPreview result={result} style={{width: '100%'}}/> <StudioPreview result={result} style={{width: '100%'}}/>
</ProDescriptions.Item> </ProDescriptions.Item>
</ProDescriptions> </ProDescriptions>
)} )}
{type==4 && ( {type == 4 && (
<ProDescriptions <ProDescriptions
column={1} column={1}
title='异常信息' title='异常信息'
> >
<ProDescriptions.Item label="JobId" > <ProDescriptions.Item label="JobId">
<Tag color="blue" key={row.jobId}> <Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId} <FireOutlined/> {row.jobId}
</Tag> </Tag>
</ProDescriptions.Item> </ProDescriptions.Item>
<ProDescriptions.Item> <ProDescriptions.Item>
<Scrollbars style={{height: '400px',width:'100%'}}> <CodeShow height={"80vh"} language={"java"} code={row.error} theme={"vs-dark"}/>
<pre className={styles.code}>{row.error}</pre>
</Scrollbars>
</ProDescriptions.Item> </ProDescriptions.Item>
</ProDescriptions> </ProDescriptions>
)} )}
</ModalForm> </Modal>
</> </>
); );
}; };
export default connect(({Studio}: {Studio: StateType}) => ({ export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current, current: Studio.current,
refs: Studio.refs, refs: Studio.refs,
}))(StudioHistory); }))(StudioHistory);
...@@ -18,23 +18,39 @@ ...@@ -18,23 +18,39 @@
*/ */
import {Typography, Divider, Badge, Empty,Tag} from "antd"; import {Badge, Button, Divider, Empty, Modal, Tag, Typography} from "antd";
import {StateType} from "@/pages/DataStudio/model"; import {StateType} from "@/pages/DataStudio/model";
import {connect} from "umi"; import {connect} from "umi";
import {FireOutlined, ScheduleOutlined} from '@ant-design/icons'; import {FireOutlined, ZoomInOutlined} from '@ant-design/icons';
import StudioSqlConfig from "@/components/Studio/StudioRightTool/StudioSqlConfig"; import {isSql} from "@/components/Studio/conf";
import {DIALECT, isSql} from "@/components/Studio/conf"; import {useState} from "react";
import CodeShow from "@/components/Common/CodeShow";
const { Title, Paragraph, Text, Link } = Typography; const {Title, Paragraph, Text, Link} = Typography;
const StudioMsg = (props:any) => { const StudioMsg = (props: any) => {
const {current} = props; const {current} = props;
const [sqlModalVisit, setSqlModalVisit] = useState(false);
const [errorModalVisit, setErrorModalVisit] = useState(false);
const handleOpenSqlModal = () => {
setSqlModalVisit(true);
};
const handleOpenErrorModal = () => {
setErrorModalVisit(true);
};
const handleCancel = () => {
setSqlModalVisit(false);
setErrorModalVisit(false);
};
const renderCommonSqlContent = () => { const renderCommonSqlContent = () => {
return (<> return (<>
<Paragraph> <Paragraph>
<blockquote> <Divider type="vertical"/>{current.console.result.startTime} <blockquote><Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime} <Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/> <Divider type="vertical"/>
{!(current.console.result.success) ? <><Badge status="error"/><Text type="danger">Error</Text></> : {!(current.console.result.success) ? <><Badge status="error"/><Text type="danger">Error</Text></> :
...@@ -55,17 +71,34 @@ const StudioMsg = (props:any) => { ...@@ -55,17 +71,34 @@ const StudioMsg = (props:any) => {
</Link> <Divider type="vertical"/>{current.console.result.startTime} </Link> <Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime} <Divider type="vertical"/>{current.console.result.endTime}
<Divider type="vertical"/> <Divider type="vertical"/>
{!(current.console.result.status==='SUCCESS') ? <><Badge status="error"/><Text type="danger">Error</Text></> : {!(current.console.result.status === 'SUCCESS') ? <><Badge status="error"/><Text
type="danger">Error</Text></> :
<><Badge status="success"/><Text type="success">Success</Text></>} <><Badge status="success"/><Text type="success">Success</Text></>}
<Divider type="vertical"/> <Divider type="vertical"/>
{current.console.result.jobConfig?.jobName&&<Text code>{current.console.result.jobConfig?.jobName}</Text>} {current.console.result.jobConfig?.jobName && <Text code>{current.console.result.jobConfig?.jobName}</Text>}
{current.console.result.jobId&& {current.console.result.jobId &&
(<> (<>
<Divider type="vertical"/> <Divider type="vertical"/>
<Tag color="blue" key={current.console.result.jobId}> <Tag color="blue" key={current.console.result.jobId}>
<FireOutlined /> {current.console.result.jobId} <FireOutlined/> {current.console.result.jobId}
</Tag> </Tag>
</>)} </>)}
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenSqlModal}
>
SQL
</Button>
{current.console.result.error ?
<Button
type="text"
icon={<ZoomInOutlined/>}
onClick={handleOpenErrorModal}
>
Error
</Button> : undefined
}
</blockquote> </blockquote>
{current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)} {current.console.result.statement && (<pre style={{height: '100px'}}>{current.console.result.statement}</pre>)}
{current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)} {current.console.result.error && (<pre style={{height: '100px'}}>{current.console.result.error}</pre>)}
...@@ -75,14 +108,38 @@ const StudioMsg = (props:any) => { ...@@ -75,14 +108,38 @@ const StudioMsg = (props:any) => {
return ( return (
<Typography> <>
{current?.task&&current.console.result.startTime?(isSql(current.task.dialect) ? renderCommonSqlContent(): <Typography>
renderFlinkSqlContent() ):<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> {current?.task && current.console.result.startTime ? (isSql(current.task.dialect) ? renderCommonSqlContent() :
} renderFlinkSqlContent()) : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
</Typography> }
</Typography>
<Modal
width={'100%'}
visible={sqlModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.statement &&
(<CodeShow height={"80vh"} language={"sql"} code={current.console.result.statement} theme={"vs-dark"}/>)}
</Modal>
<Modal
width={'100%'}
visible={errorModalVisit}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{current.console.result.error &&
(<CodeShow height={"80vh"} language={"java"} code={current.console.result.error} theme={"vs-dark"}/>)}
</Modal>
</>
); );
}; };
export default connect(({ Studio }: { Studio: StateType }) => ({ export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current, current: Studio.current,
}))(StudioMsg); }))(StudioMsg);
...@@ -30,25 +30,25 @@ import Tables from "@/pages/DataBase/Tables"; ...@@ -30,25 +30,25 @@ import Tables from "@/pages/DataBase/Tables";
import {TreeDataNode} from "@/components/Studio/StudioTree/Function"; import {TreeDataNode} from "@/components/Studio/StudioTree/Function";
import Generation from "@/pages/DataBase/Generation"; import Generation from "@/pages/DataBase/Generation";
const { DirectoryTree } = Tree; const {DirectoryTree} = Tree;
const {Option} = Select; const {Option} = Select;
const { TabPane } = Tabs; const {TabPane} = Tabs;
const StudioMetaData = (props: any) => { const StudioMetaData = (props: any) => {
const {database,toolHeight, dispatch} = props; const {database, toolHeight, dispatch} = props;
const [databaseId, setDatabaseId] = useState<number>(); const [databaseId, setDatabaseId] = useState<number>();
const [treeData, setTreeData] = useState<[]>([]); const [treeData, setTreeData] = useState<[]>([]);
const [modalVisit, setModalVisit] = useState(false); const [modalVisit, setModalVisit] = useState(false);
const [row, setRow] = useState<TreeDataNode>(); const [row, setRow] = useState<TreeDataNode>();
const onRefreshTreeData = (databaseId: number)=>{ const onRefreshTreeData = (databaseId: number) => {
if(!databaseId)return; if (!databaseId) return;
setDatabaseId(databaseId); setDatabaseId(databaseId);
const res = showMetaDataTable(databaseId); const res = showMetaDataTable(databaseId);
res.then((result) => { res.then((result) => {
let tables = result.datas; let tables = result.datas;
if(tables) { if (tables) {
for (let i = 0; i < tables.length; i++) { for (let i = 0; i < tables.length; i++) {
tables[i].title = tables[i].name; tables[i].title = tables[i].name;
tables[i].key = tables[i].name; tables[i].key = tables[i].name;
...@@ -64,26 +64,27 @@ const StudioMetaData = (props: any) => { ...@@ -64,26 +64,27 @@ const StudioMetaData = (props: any) => {
} }
} }
setTreeData(tables); setTreeData(tables);
}else{ } else {
setTreeData([]); setTreeData([]);
} }
}); });
}; };
const onChangeDataBase = (value: number)=>{ const onChangeDataBase = (value: number) => {
onRefreshTreeData(value); onRefreshTreeData(value);
}; };
const getDataBaseOptions = ()=>{ const getDataBaseOptions = () => {
return <>{database.map(({ id, name, alias, type, enabled }) => ( return <>{database.map(({id, name, alias, type, enabled}) => (
<Option value={id} label={<><Tag color={enabled ? "processing" : "error"}>{type}</Tag>{ alias === "" ? name:alias}</>}> <Option value={id}
<Tag color={enabled ? "processing" : "error"}>{type}</Tag>{ alias === "" ? name:alias} label={<><Tag color={enabled ? "processing" : "error"}>{type}</Tag>{alias === "" ? name : alias}</>}>
<Tag color={enabled ? "processing" : "error"}>{type}</Tag>{alias === "" ? name : alias}
</Option> </Option>
))}</> ))}</>
}; };
const openColumnInfo = (e: React.MouseEvent, node: TreeDataNode) => { const openColumnInfo = (e: React.MouseEvent, node: TreeDataNode) => {
if(node.isLeaf){ if (node.isLeaf) {
setRow(node); setRow(node);
setModalVisit(true); setModalVisit(true);
} }
...@@ -105,21 +106,21 @@ const StudioMetaData = (props: any) => { ...@@ -105,21 +106,21 @@ const StudioMetaData = (props: any) => {
{getDataBaseOptions()} {getDataBaseOptions()}
</Select> </Select>
<Scrollbars style={{height: (toolHeight - 32)}}> <Scrollbars style={{height: (toolHeight - 32)}}>
{treeData.length>0?( {treeData.length > 0 ? (
<DirectoryTree <DirectoryTree
showIcon showIcon
switcherIcon={<DownOutlined/>} switcherIcon={<DownOutlined/>}
treeData={treeData} treeData={treeData}
onRightClick={({event, node}: any) => { onRightClick={({event, node}: any) => {
openColumnInfo(event, node) openColumnInfo(event, node)
}} }}
/>):(<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />)} />) : (<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>)}
</Scrollbars> </Scrollbars>
<Modal <Modal
title={row?.key} title={row?.key}
visible={modalVisit} visible={modalVisit}
width={1000} width={1000}
onCancel={()=>{ onCancel={() => {
cancelHandle(); cancelHandle();
}} }}
footer={[ footer={[
...@@ -134,38 +135,40 @@ const StudioMetaData = (props: any) => { ...@@ -134,38 +135,40 @@ const StudioMetaData = (props: any) => {
<TabPane <TabPane
tab={ tab={
<span> <span>
<TableOutlined /> <TableOutlined/>
表信息 表信息
</span> </span>
} }
key="tableInfo" key="tableInfo"
> >
{row?<Tables table={row}/>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />} {row ? <Tables table={row}/> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</TabPane> </TabPane>
<TabPane <TabPane
tab={ tab={
<span> <span>
<CodepenOutlined /> <CodepenOutlined/>
字段信息 字段信息
</span> </span>
} }
key="columnInfo" key="columnInfo"
> >
{row? <Columns dbId={databaseId} schema={row.schema} table={row.table}/> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />} {row ? <Columns dbId={databaseId} schema={row.schema} table={row.table} scroll={{x: 1000}}/> :
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</TabPane> </TabPane>
<TabPane <TabPane
tab={ tab={
<span> <span>
<OrderedListOutlined /> <OrderedListOutlined/>
SQL 生成 SQL 生成
</span> </span>
} }
key="sqlGeneration" key="sqlGeneration"
> >
{row? <Generation dbId={databaseId} schema={row.schema} table={row.table}/> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />} {row ? <Generation dbId={databaseId} schema={row.schema} table={row.table}/> :
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</TabPane> </TabPane>
</Tabs> </Tabs>
</Modal> </Modal>
</> </>
); );
}; };
......
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
*/ */
import {message, Tabs, Menu, Dropdown} from 'antd'; import {Dropdown, Menu, message, Tabs} from 'antd';
import React, {useState} from 'react'; import React from 'react';
import {connect} from 'umi'; import {connect} from 'umi';
import {StateType} from '@/pages/DataStudio/model'; import {StateType} from '@/pages/DataStudio/model';
import styles from './index.less'; import styles from './index.less';
...@@ -31,7 +31,7 @@ import {Dispatch} from "@@/plugin-dva/connect"; ...@@ -31,7 +31,7 @@ import {Dispatch} from "@@/plugin-dva/connect";
const {TabPane} = Tabs; const {TabPane} = Tabs;
const EditorTabs = (props: any) => { const EditorTabs = (props: any) => {
const {tabs, current, toolHeight, width,height} = props; const {tabs, current, toolHeight, width, height} = props;
const onChange = (activeKey: any) => { const onChange = (activeKey: any) => {
props.saveToolHeight(toolHeight); props.saveToolHeight(toolHeight);
...@@ -42,7 +42,7 @@ const EditorTabs = (props: any) => { ...@@ -42,7 +42,7 @@ const EditorTabs = (props: any) => {
if (action === 'add') { if (action === 'add') {
add(); add();
} else if (action === 'remove') { } else if (action === 'remove') {
props.saveToolHeight(toolHeight-0.0001); props.saveToolHeight(toolHeight - 0.0001);
// if (current.isModified) { // if (current.isModified) {
// saveTask(current, dispatch); // saveTask(current, dispatch);
// } // }
...@@ -71,11 +71,11 @@ const EditorTabs = (props: any) => { ...@@ -71,11 +71,11 @@ const EditorTabs = (props: any) => {
newActiveKey = newPanes[0].key; newActiveKey = newPanes[0].key;
} }
} }
props.saveTabs(newPanes,newActiveKey); props.saveTabs(newPanes, newActiveKey);
}; };
const handleClickMenu = (e: any, current) => { const handleClickMenu = (e: any, current) => {
props.closeTabs(current,e.key); props.closeTabs(current, e.key);
}; };
const menu = (pane) => ( const menu = (pane) => (
...@@ -92,11 +92,11 @@ const EditorTabs = (props: any) => { ...@@ -92,11 +92,11 @@ const EditorTabs = (props: any) => {
const Tab = (pane: any) => ( const Tab = (pane: any) => (
<span> <span>
{pane.key === 0 ? ( {pane.key === 0 ? (
pane.title <>{pane.icon} {pane.title}</>
) : ( ) : (
<Dropdown overlay={menu(pane)} trigger={['contextMenu']}> <Dropdown overlay={menu(pane)} trigger={['contextMenu']}>
<span className="ant-dropdown-link"> <span className="ant-dropdown-link">
{pane.title} <>{pane.icon} {pane.title}</>
</span> </span>
</Dropdown> </Dropdown>
)} )}
...@@ -105,52 +105,52 @@ const EditorTabs = (props: any) => { ...@@ -105,52 +105,52 @@ const EditorTabs = (props: any) => {
return ( return (
<> <>
{tabs.panes.length === 0?<StudioHome width={width} />: {tabs.panes.length === 0 ? <StudioHome width={width}/> :
<Tabs <Tabs
hideAdd hideAdd
type="editable-card" type="editable-card"
size="small" size="small"
onChange={onChange} onChange={onChange}
activeKey={tabs.activeKey + ''} activeKey={tabs.activeKey + ''}
onEdit={onEdit} onEdit={onEdit}
className={styles['edit-tabs']} className={styles['edit-tabs']}
style={{height: height?height:toolHeight}} style={{height: height ? height : toolHeight}}
> >
{tabs.panes.map((pane,i) => ( {tabs.panes.map((pane, i) => (
<TabPane tab={Tab(pane)} key={pane.key} closable={pane.closable}> <TabPane tab={Tab(pane)} key={pane.key} closable={pane.closable}>
<StudioEdit <StudioEdit
tabsKey={pane.key} tabsKey={pane.key}
sql={pane.value} sql={pane.value}
monaco={pane.monaco} monaco={pane.monaco}
// sqlMetaData={pane.sqlMetaData} // sqlMetaData={pane.sqlMetaData}
height={height?height:(toolHeight - 32)} height={height ? height : (toolHeight - 32)}
width={width} width={width}
language={current.task.dialect === DIALECT.JAVA ? 'java' : 'sql'} language={current.task.dialect === DIALECT.JAVA ? 'java' : 'sql'}
/> />
</TabPane> </TabPane>
))} ))}
</Tabs>} </Tabs>}
</> </>
); );
}; };
const mapDispatchToProps = (dispatch: Dispatch)=>({ const mapDispatchToProps = (dispatch: Dispatch) => ({
closeTabs:(current: any,key: string)=>dispatch({ closeTabs: (current: any, key: string) => dispatch({
type: 'Studio/closeTabs', type: 'Studio/closeTabs',
payload: { payload: {
deleteType: key, deleteType: key,
current current
}, },
}),saveTabs:(newPanes: any,newActiveKey: number)=>dispatch({ }), saveTabs: (newPanes: any, newActiveKey: number) => dispatch({
type: 'Studio/saveTabs', type: 'Studio/saveTabs',
payload: { payload: {
activeKey: newActiveKey, activeKey: newActiveKey,
panes: newPanes, panes: newPanes,
}, },
}),saveToolHeight:(toolHeight: number)=>dispatch({ }), saveToolHeight: (toolHeight: number) => dispatch({
type: 'Studio/saveToolHeight', type: 'Studio/saveToolHeight',
payload: toolHeight - 0.0001, payload: toolHeight - 0.0001,
}),changeActiveKey:(activeKey: number)=>dispatch({ }), changeActiveKey: (activeKey: number) => dispatch({
type: 'Studio/changeActiveKey', type: 'Studio/changeActiveKey',
payload: activeKey, payload: activeKey,
}), }),
...@@ -161,4 +161,4 @@ export default connect(({Studio}: { Studio: StateType }) => ({ ...@@ -161,4 +161,4 @@ export default connect(({Studio}: { Studio: StateType }) => ({
sql: Studio.sql, sql: Studio.sql,
tabs: Studio.tabs, tabs: Studio.tabs,
toolHeight: Studio.toolHeight, toolHeight: Studio.toolHeight,
}),mapDispatchToProps)(EditorTabs); }), mapDispatchToProps)(EditorTabs);
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
*/ */
import React, {useEffect, useState} from 'react'; import React, {useState} from 'react';
import {Form, Button, Input, Modal} from 'antd'; import {Button, Form, Input, Modal} from 'antd';
import type {CatalogueTableListItem} from '../data.d'; import type {CatalogueTableListItem} from '../data.d';
...@@ -40,6 +40,7 @@ const formLayout = { ...@@ -40,6 +40,7 @@ const formLayout = {
const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => { const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => {
const [formVals, setFormVals] = useState<Partial<CatalogueTableListItem>>({ const [formVals, setFormVals] = useState<Partial<CatalogueTableListItem>>({
id: props.values.id, id: props.values.id,
taskId: props.values.taskId,
name: props.values.name, name: props.values.name,
isLeaf: props.values.isLeaf, isLeaf: props.values.isLeaf,
parentId: props.values.parentId, parentId: props.values.parentId,
...@@ -100,6 +101,7 @@ const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => { ...@@ -100,6 +101,7 @@ const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => {
form={form} form={form}
initialValues={{ initialValues={{
id: formVals.id, id: formVals.id,
taskId: formVals.taskId,
name: formVals.name, name: formVals.name,
isLeaf: formVals.isLeaf, isLeaf: formVals.isLeaf,
parentId: formVals.parentId, parentId: formVals.parentId,
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
export type CatalogueTableListItem = { export type CatalogueTableListItem = {
id: number, id: number,
taskId: number,
name: string, name: string,
isLeaf: string, isLeaf: string,
parentId: number, parentId: number,
......
...@@ -230,9 +230,10 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => { ...@@ -230,9 +230,10 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
result.then(result => { result.then(result => {
let newTabs = tabs; let newTabs = tabs;
let newPane: any = { let newPane: any = {
title: <>{node!.icon} {node!.name}</>, title: node!.name,
key: node!.taskId, key: node!.taskId,
value: (result.datas.statement ? result.datas.statement : ''), value: (result.datas.statement ? result.datas.statement : ''),
icon: node!.icon,
closable: true, closable: true,
path: node!.path, path: node!.path,
task: { task: {
...@@ -310,6 +311,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => { ...@@ -310,6 +311,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
setActiveNode(node); setActiveNode(node);
setCatalogueFormValues({ setCatalogueFormValues({
id: node?.id, id: node?.id,
taskId: node?.taskId,
name: node?.name, name: node?.name,
}); });
}; };
...@@ -656,13 +658,16 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => { ...@@ -656,13 +658,16 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
handleUpdateCatalogueModalVisible(false); handleUpdateCatalogueModalVisible(false);
setCatalogueFormValues({}); setCatalogueFormValues({});
getTreeData(); getTreeData();
dispatch({ if (value.taskId) {
type: "Studio/renameTab", dispatch({
payload: { type: "Studio/renameTab",
key: value.id, payload: {
name: <>{activeNode.icon} {value.name}</> key: value.taskId,
}, title: value.name,
}); icon: activeNode.icon
},
});
}
} }
}} }}
onCancel={() => { onCancel={() => {
......
...@@ -19,12 +19,12 @@ ...@@ -19,12 +19,12 @@
import React from "react"; import React from "react";
import {KeyOutlined, CheckSquareOutlined} from '@ant-design/icons'; import {CheckSquareOutlined, KeyOutlined} from '@ant-design/icons';
import DTable from "@/components/Common/DTable"; import DTable from "@/components/Common/DTable";
const Columns = (props: any) => { const Columns = (props: any) => {
const {dbId,table,schema} = props; const {dbId, table, schema, scroll} = props;
const cols = [{ const cols = [{
title: '序号', title: '序号',
...@@ -50,7 +50,7 @@ const Columns = (props: any) => { ...@@ -50,7 +50,7 @@ const Columns = (props: any) => {
dataIndex: 'keyFlag', dataIndex: 'keyFlag',
render: (_, record) => ( render: (_, record) => (
<> <>
{record.keyFlag?<KeyOutlined style={{ color:'#FAA100'}} />:undefined} {record.keyFlag ? <KeyOutlined style={{color: '#FAA100'}}/> : undefined}
</> </>
), ),
filters: [ filters: [
...@@ -64,12 +64,12 @@ const Columns = (props: any) => { ...@@ -64,12 +64,12 @@ const Columns = (props: any) => {
}, },
], ],
openSearch: 'dict', openSearch: 'dict',
},{ }, {
title: '自增', title: '自增',
dataIndex: 'autoIncrement', dataIndex: 'autoIncrement',
render: (_, record) => ( render: (_, record) => (
<> <>
{record.autoIncrement?<CheckSquareOutlined style={{ color:'#1296db'}} />:undefined} {record.autoIncrement ? <CheckSquareOutlined style={{color: '#1296db'}}/> : undefined}
</> </>
), ),
filters: [ filters: [
...@@ -83,12 +83,12 @@ const Columns = (props: any) => { ...@@ -83,12 +83,12 @@ const Columns = (props: any) => {
}, },
], ],
openSearch: 'dict', openSearch: 'dict',
},{ }, {
title: '非空', title: '非空',
dataIndex: 'nullable', dataIndex: 'nullable',
render: (_, record) => ( render: (_, record) => (
<> <>
{!record.nullable?<CheckSquareOutlined style={{ color:'#1296db'}} />:undefined} {!record.nullable ? <CheckSquareOutlined style={{color: '#1296db'}}/> : undefined}
</> </>
), ),
filters: [ filters: [
...@@ -102,30 +102,31 @@ const Columns = (props: any) => { ...@@ -102,30 +102,31 @@ const Columns = (props: any) => {
}, },
], ],
openSearch: 'dict', openSearch: 'dict',
},{ }, {
title: '默认值', title: '默认值',
dataIndex: 'defaultValue', dataIndex: 'defaultValue',
},{ }, {
title: '精度', title: '精度',
dataIndex: 'precision', dataIndex: 'precision',
isString: false, isString: false,
},{ }, {
title: '小数范围', title: '小数范围',
dataIndex: 'scale', dataIndex: 'scale',
isString: false, isString: false,
},{ }, {
title: '字符集', title: '字符集',
dataIndex: 'characterSet', dataIndex: 'characterSet',
},{ }, {
title: '排序规则', title: '排序规则',
dataIndex: 'collation', dataIndex: 'collation',
},{ }, {
title: 'Java 类型', title: 'Java 类型',
dataIndex: 'javaType', dataIndex: 'javaType',
},] },]
return ( return (
<DTable columns={cols} <DTable columns={cols}
dataSource={{url:'api/database/listColumns',params:{id:dbId,schemaName:schema,tableName:table}}}/> scroll={scroll}
dataSource={{url: 'api/database/listColumns', params: {id: dbId, schemaName: schema, tableName: table}}}/>
) )
}; };
......
...@@ -19,9 +19,7 @@ ...@@ -19,9 +19,7 @@
import type {Effect, Reducer} from "umi"; import type {Effect, Reducer} from "umi";
import { import {handleAddOrUpdate} from "@/components/Common/crud";
handleAddOrUpdate
} from "@/components/Common/crud";
import type {SqlMetaData} from "@/components/Studio/StudioEvent/data"; import type {SqlMetaData} from "@/components/Studio/StudioEvent/data";
export type ClusterType = { export type ClusterType = {
...@@ -124,6 +122,7 @@ export type TabsItemType = { ...@@ -124,6 +122,7 @@ export type TabsItemType = {
title: string; title: string;
key: number, key: number,
value: string; value: string;
icon: any;
closable: boolean; closable: boolean;
path: string[]; path: string[];
task?: TaskType; task?: TaskType;
...@@ -629,19 +628,23 @@ const Model: ModelType = { ...@@ -629,19 +628,23 @@ const Model: ModelType = {
let newCurrent = state.current; let newCurrent = state.current;
for (let i = 0; i < newTabs.panes.length; i++) { for (let i = 0; i < newTabs.panes.length; i++) {
if (newTabs.panes[i].key == payload.key) { if (newTabs.panes[i].key == payload.key) {
newTabs.panes[i].title = payload.name; newTabs.panes[i].title = payload.title;
newTabs.panes[i].task.alias = payload.name; newTabs.panes[i].icon = payload.icon;
newTabs.panes[i].task.alias = payload.title;
newTabs.panes[i].path[newTabs.panes[i].path.length - 1] = payload.title;
} }
if (newTabs.panes[i].key == newCurrent.key) { if (newTabs.panes[i].key == newCurrent.key) {
newCurrent.title = payload.name; newCurrent.title = payload.title;
newCurrent.task.alias = payload.name; newCurrent.icon = payload.icon;
newCurrent.task.alias = payload.title;
newCurrent.path[newCurrent.path.length - 1] = payload.title;
} }
} }
if (newTabs.panes.length == 0) { if (newTabs.panes.length == 0) {
return { return {
...state, ...state,
current: undefined, current: undefined,
tabs: newTabs, tabs: {...newTabs},
currentPath: ['引导页'], currentPath: ['引导页'],
}; };
} }
......
...@@ -1630,6 +1630,24 @@ export default (): React.ReactNode => { ...@@ -1630,6 +1630,24 @@ export default (): React.ReactNode => {
<li> <li>
<Link>优化部署文档</Link> <Link>优化部署文档</Link>
</li> </li>
<li>
<Link>修复 yarn-application 任务分隔符错误</Link>
</li>
<li>
<Link>升级 Flink 1.15 版本为 1.15.2</Link>
</li>
<li>
<Link>优化 SqlServer 字段类型查询</Link>
</li>
<li>
<Link>修复重命名作业后保存作业失败</Link>
</li>
<li>
<Link>修复提交历史的第二次弹框时无内容</Link>
</li>
<li>
<Link>新增数据开发任务信息日志详情按钮</Link>
</li>
</ul> </ul>
</Paragraph> </Paragraph>
</Timeline.Item> </Timeline.Item>
......
...@@ -81,6 +81,7 @@ WITH 参数通常用于指定 CDCSOURCE 所需参数,语法为`'key1'='value1' ...@@ -81,6 +81,7 @@ WITH 参数通常用于指定 CDCSOURCE 所需参数,语法为`'key1'='value1'
| sink.table.upper | 否 | 无 | 目标表的表名全大写 | | sink.table.upper | 否 | 无 | 目标表的表名全大写 |
| sink.table.lower | 否 | 无 | 目标表的表名全小写 | | sink.table.lower | 否 | 无 | 目标表的表名全小写 |
| sink.* | 否 | 无 | 目标数据源的配置信息,同 FlinkSQL,使用 ${schemaName} 和 ${tableName} 可注入经过处理的源表名 | | sink.* | 否 | 无 | 目标数据源的配置信息,同 FlinkSQL,使用 ${schemaName} 和 ${tableName} 可注入经过处理的源表名 |
| sink[N].* | 否 | 无 | N代表为多目的地写入, 默认从0开始到N, 其他配置参数信息参考sink.*的配置. |
## 示例 ## 示例
...@@ -226,7 +227,36 @@ EXECUTE CDCSOURCE demo WITH ( ...@@ -226,7 +227,36 @@ EXECUTE CDCSOURCE demo WITH (
) )
``` ```
同时将CDCSOURCE数据写入到Doirs和Kafka
```sql
EXECUTE CDCSOURCE jobname WITH (
'connector' = 'mysql-cdc',
'hostname' = '127.0.0.1',
'port' = '3306',
'username' = 'dlink',
'password' = 'dlink',
'checkpoint' = '3000',
'scan.startup.mode' = 'initial',
'parallelism' = '1',
'table-name' = 'test\.student,test\.score',
'sink[0].connector' = 'doris',
'sink[0].fenodes' = '127.0.0.1:8030',
'sink[0].username' = 'root',
'sink[0].password' = 'dw123456',
'sink[0].sink.batch.size' = '1',
'sink[0].sink.max-retries' = '1',
'sink[0].sink.batch.interval' = '60000',
'sink[0].sink.db' = 'test',
'sink[0].table.prefix' = 'ODS_',
'sink[0].table.upper' = 'true',
'sink[0].table.identifier' = '${schemaName}.${tableName}',
'sink[0].sink.enable-delete' = 'true',
'sink[1].connector'='datastream-kafka',
'sink[1].topic'='dlinkcdc',
'sink[1].brokers'='127.0.0.1:9092'
)
```
:::tip 说明 :::tip 说明
...@@ -235,4 +265,4 @@ EXECUTE CDCSOURCE demo WITH ( ...@@ -235,4 +265,4 @@ EXECUTE CDCSOURCE demo WITH (
- 禁用全局变量、语句集、批模式。 - 禁用全局变量、语句集、批模式。
- 目前不支持 Application 模式,后续支持。 - 目前不支持 Application 模式,后续支持。
::: :::
\ No newline at end of file
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