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 @@
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
......
......@@ -25,7 +25,7 @@
</includes>
</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>
<includes>
<include>*.jar</include>
......
......@@ -56,8 +56,8 @@ public class SqlServerCDCBuilder extends AbstractCDCBuilder implements CDCBuilde
String database = config.getDatabase();
Properties debeziumProperties = new Properties();
// 为部分转换添加默认值
//debeziumProperties.setProperty("bigint.unsigned.handling.mode", "long");
//debeziumProperties.setProperty("decimal.handling.mode", "string");
debeziumProperties.setProperty("bigint.unsigned.handling.mode", "long");
debeziumProperties.setProperty("decimal.handling.mode", "string");
for (Map.Entry<String, String> entry : config.getDebezium().entrySet()) {
if (Asserts.isNotNullString(entry.getKey()) && Asserts.isNotNullString(entry.getValue())) {
debeziumProperties.setProperty(entry.getKey(), entry.getValue());
......
......@@ -49,19 +49,19 @@ public final class FlinkRestAPIConstant {
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 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 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=";
......
......@@ -33,16 +33,17 @@ public interface SqlServerConstant {
* 查询列信息模板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, "
+ " 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 "
+ " 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 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, "
+ "'' as AUTO_INCREMENT "
+ "FROM ( select name,object_id from sys.tables UNION all select name,object_id from sys.views ) a INNER JOIN sys.columns b "
+ " 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' ";
+ " 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 "
+ " 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 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, b.PRECISION as NUMERIC_PRECISION, b.scale as NUMERIC_SCALE,"
+ "'' as AUTO_INCREMENT "
+ "FROM ( select name,object_id from sys.tables UNION all select name,object_id from sys.views ) a INNER JOIN sys.columns b "
+ " 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
......
......@@ -33,8 +33,8 @@ public class SqlServerTypeConvert implements ITypeConvert {
String t = column.getType().toLowerCase();
boolean isNullable = !column.isKeyFlag() && column.isNullable();
if (t.contains("char") || t.contains("varchar") || t.contains("text")
|| t.contains("nchar") || t.contains("nvarchar") || t.contains("ntext")
|| t.contains("uniqueidentifier") || t.contains("sql_variant")) {
|| t.contains("nchar") || t.contains("nvarchar") || t.contains("ntext")
|| t.contains("uniqueidentifier") || t.contains("sql_variant")) {
columnType = ColumnType.STRING;
} else if (t.contains("bigint")) {
if (isNullable) {
......@@ -60,8 +60,7 @@ public class SqlServerTypeConvert implements ITypeConvert {
} else {
columnType = ColumnType.DOUBLE;
}
} else if (t.contains("decimal") || t.contains("money") || t.contains("smallmoney")
|| t.contains("numeric")) {
} else if (t.contains("decimal") || t.contains("money") || t.contains("smallmoney") || t.contains("numeric")) {
columnType = ColumnType.DECIMAL;
} else if (t.contains("real")) {
if (isNullable) {
......@@ -69,10 +68,18 @@ public class SqlServerTypeConvert implements ITypeConvert {
} else {
columnType = ColumnType.FLOAT;
}
} else if (t.contains("date")) {
columnType = ColumnType.DATE;
} else if (t.contains("smalldatetime") || t.contains("datetime")) {
} else if (t.equalsIgnoreCase("datetime") || t.equalsIgnoreCase("smalldatetime")) {
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")) {
columnType = ColumnType.BYTES;
}
......
......@@ -19,12 +19,11 @@
import MonacoEditor from "react-monaco-editor";
import * as _monaco from "monaco-editor";
export type CodeShowFormProps = {
height?: string;
width?: string;
language: string;
language?: string;
theme?: string;
options?: any;
code: string;
......@@ -40,8 +39,8 @@ const CodeShow = (props: CodeShowFormProps) => {
options = {
selectOnLineNumbers: true,
renderSideBySide: false,
autoIndent:'None',
readOnly:true ,
autoIndent: 'None',
readOnly: true,
},
code,
} = props;
......
......@@ -26,50 +26,51 @@ import {Button, Input, Space} from "antd";
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);
setData(msg.datas);
};
const buildColumn = () =>{
const columnList: any=[];
const buildColumn = () => {
const columnList: any = [];
columns.map((item) => {
const openSorter = item.openSorter==null?true:item.openSorter;
const isString = item.isString==null?true:item.isString;
const openSearch = item.openSearch==null?'like':item.openSearch;
const openSorter = item.openSorter == null ? true : item.openSorter;
const isString = item.isString == null ? true : item.isString;
const openSearch = item.openSearch == null ? 'like' : item.openSearch;
let column = {
title: item.title?item.title:item.field,
dataIndex: item.dataIndex?item.dataIndex:item.field,
key: item.dataIndex?item.dataIndex:item.field,
title: item.title ? item.title : item.field,
dataIndex: item.dataIndex ? item.dataIndex : item.field,
key: item.dataIndex ? item.dataIndex : item.field,
};
if(openSorter){
if(isString){
if (openSorter) {
if (isString) {
column = {
sorter: (a, b) => {
const value1 = a[column.dataIndex]!=null?a[column.dataIndex].toString():'';
const value2 = b[column.dataIndex]!=null?b[column.dataIndex].toString():'';
const value1 = a[column.dataIndex] != null ? a[column.dataIndex].toString() : '';
const value2 = b[column.dataIndex] != null ? b[column.dataIndex].toString() : '';
return value1.localeCompare(value2);
},
...column,
}
}else{
} else {
column = {
sorter: (a, b) => a[column.dataIndex] - b[column.dataIndex],
...column,
}
}
}
if(openSearch==='like'){
column = {...column,...getColumnSearchProps(column.dataIndex),}
}else if(openSearch==='dict'){
if (openSearch === 'like') {
column = {...column, ...getColumnSearchProps(column.dataIndex),}
} else if (openSearch === 'dict') {
column = {
onFilter: (value, record) => record[column.dataIndex] === value,
...column,}
...column,
}
}
columnList.push({
...column,
......@@ -80,7 +81,7 @@ const DTable = (props: any) => {
}
useEffect(() => {
if(dataSource&&dataSource.url){
if (dataSource && dataSource.url) {
refreshData();
}
}, [dataSource]);
......@@ -89,7 +90,8 @@ const DTable = (props: any) => {
<ProTable
columns={buildColumn()}
style={{width: '100%'}}
dataSource={dataSource?(dataSource.url?data:dataSource):[]}
scroll={scroll}
dataSource={dataSource ? (dataSource.url ? data : dataSource) : []}
rowKey="name"
pagination={{
pageSize: 10,
......
......@@ -26,18 +26,15 @@ import ProList from '@ant-design/pro-list';
import {handleRemove, queryData} from "@/components/Common/crud";
import ProDescriptions from '@ant-design/pro-descriptions';
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 {getJobData} from "@/pages/DataStudio/service";
import {HistoryItem} from "@/components/Studio/StudioConsole/StudioHistory/data";
import CodeShow from "@/components/Common/CodeShow";
const { Title, Paragraph, Text, Link } = Typography;
const {Title, Paragraph, Text, Link} = Typography;
type HistoryConfig={
type HistoryConfig = {
useSession: boolean;
session: string;
useRemote: boolean;
......@@ -62,41 +59,44 @@ type HistoryConfig={
const url = '/api/history';
const StudioHistory = (props: any) => {
const {current,refs,dispatch} = props;
const {current, refs, dispatch} = props;
const [modalVisit, setModalVisit] = useState(false);
const [row, setRow] = useState<HistoryItem>();
const [config,setConfig] = useState<HistoryConfig>();
const [type,setType] = useState<number>();
const [result,setResult] = useState<{}>();
const [config, setConfig] = useState<HistoryConfig>();
const [type, setType] = useState<number>();
const [result, setResult] = useState<{}>();
const showDetail=(row:HistoryItem,type:number)=>{
const showDetail = (row: HistoryItem, type: number) => {
setRow(row);
setModalVisit(true);
setType(type);
setConfig(JSON.parse(row.configJson));
if(type===3){
if (type === 3) {
// showJobData(row.jobId,dispatch)
const res = getJobData(row.jobId);
res.then((resd)=>{
res.then((resd) => {
setResult(resd.datas);
});
}
};
const removeHistory=(row:HistoryItem)=>{
const removeHistory = (row: HistoryItem) => {
Modal.confirm({
title: '删除执行记录',
content: '确定删除该执行记录吗?',
okText: '确认',
cancelText: '取消',
onOk:async () => {
await handleRemove(url,[row]);
// refs.current?.reloadAndRest?.();
onOk: async () => {
await handleRemove(url, [row]);
refs.history?.current?.reload();
}
});
};
const handleCancel = () => {
setModalVisit(false);
};
return (
<>
<ProList<HistoryItem>
......@@ -111,7 +111,7 @@ const StudioHistory = (props: any) => {
}}
rowKey="id"
headerTitle="执行历史"
request={(params, sorter, filter) => queryData(url,{...params, sorter:{id:'descend'}, filter})}
request={(params, sorter, filter) => queryData(url, {...params, sorter: {id: 'descend'}, filter})}
pagination={{
pageSize: 5,
}}
......@@ -124,7 +124,7 @@ const StudioHistory = (props: any) => {
return (
<Space size={0}>
<Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId}
<FireOutlined/> {row.jobId}
</Tag>
</Space>
);
......@@ -132,7 +132,7 @@ const StudioHistory = (props: any) => {
},
description: {
search: false,
render:(_, row)=>{
render: (_, row) => {
return (<Paragraph>
<blockquote>
<Link href={`http://${row.jobManagerAddress}`} target="_blank">
......@@ -148,37 +148,37 @@ const StudioHistory = (props: any) => {
render: (_, row) => {
return (
<Space size={0}>
{row.jobName?(
{row.jobName ? (
<Tag color="gray" key={row.jobName}>
{row.jobName}
</Tag>
):''}
{row.session?(
) : ''}
{row.session ? (
<Tag color="orange" key={row.session}>
<MessageOutlined /> {row.session}
<MessageOutlined/> {row.session}
</Tag>
):''}
{row.clusterAlias?(
) : ''}
{row.clusterAlias ? (
<Tag color="green" key={row.clusterAlias}>
<ClusterOutlined /> {row.clusterAlias}
<ClusterOutlined/> {row.clusterAlias}
</Tag>
):(<Tag color="green" key={row.clusterAlias}>
<ClusterOutlined /> 本地环境
) : (<Tag color="green" key={row.clusterAlias}>
<ClusterOutlined/> 本地环境
</Tag>)}
{row.type?(
{row.type ? (
<Tag color="blue" key={row.type}>
<RocketOutlined /> {row.type}
<RocketOutlined/> {row.type}
</Tag>
):''}
{(row.status==2) ?
(<><Badge status="success"/><Text type="success">SUCCESS</Text></>):
(row.status==1) ?
) : ''}
{(row.status == 2) ?
(<><Badge status="success"/><Text type="success">SUCCESS</Text></>) :
(row.status == 1) ?
<><Badge status="success"/><Text type="secondary">RUNNING</Text></> :
(row.status==3) ?
(row.status == 3) ?
<><Badge status="error"/><Text type="danger">FAILED</Text></> :
(row.status==4) ?
(row.status == 4) ?
<><Badge status="error"/><Text type="warning">CANCEL</Text></> :
(row.status==0) ?
(row.status == 0) ?
<><Badge status="error"/><Text type="warning">INITIALIZE</Text></> :
<><Badge status="success"/><Text type="danger">UNKNOWEN</Text></>}
</Space>
......@@ -188,19 +188,29 @@ const StudioHistory = (props: any) => {
},
actions: {
render: (text, row) => [
<a key="config" onClick={()=>{showDetail(row,1)}}>
<a key="config" onClick={() => {
showDetail(row, 1)
}}>
执行配置
</a>,
<a key="statement" onClick={()=>{showDetail(row,2)}}>
<a key="statement" onClick={() => {
showDetail(row, 2)
}}>
FlinkSql语句
</a>,
<a key="result" onClick={()=>{showDetail(row,3)}}>
<a key="result" onClick={() => {
showDetail(row, 3)
}}>
预览数据
</a>,
<a key="error" onClick={()=>{showDetail(row,4)}}>
<a key="error" onClick={() => {
showDetail(row, 4)
}}>
异常信息
</a>,
<a key="delete" onClick={()=>{removeHistory(row)}}>
<a key="delete" onClick={() => {
removeHistory(row)
}}>
删除
</a>,
],
......@@ -259,40 +269,35 @@ const StudioHistory = (props: any) => {
}}
options={{
search: false,
setting:false
setting: false
}}
/>
<ModalForm
<Modal
width={'80%'}
visible={modalVisit}
onFinish={async () => {
}}
onVisibleChange={setModalVisit}
submitter={{
submitButtonProps: {
style: {
display: 'none',
},
},
}}
destroyOnClose
centered
footer={false}
onCancel={handleCancel}
>
{type==1 && (
{type == 1 && (
<ProDescriptions
column={2}
title='执行配置'
>
<ProDescriptions.Item span={2} label="JobId" >
<ProDescriptions.Item span={2} label="JobId">
<Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId}
<FireOutlined/> {row.jobId}
</Tag>
</ProDescriptions.Item>
<ProDescriptions.Item label="共享会话" >
{config.useSession?'启用':'禁用'}
<ProDescriptions.Item label="共享会话">
{config.useSession ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item label="会话 Key">
{config.session}
</ProDescriptions.Item>
<ProDescriptions.Item label="执行方式" >
{config.useRemote?'远程':'本地'}
<ProDescriptions.Item label="执行方式">
{config.useRemote ? '远程' : '本地'}
</ProDescriptions.Item>
<ProDescriptions.Item label="任务类型">
{config.type}
......@@ -303,17 +308,17 @@ const StudioHistory = (props: any) => {
<ProDescriptions.Item label="集群配置ID">
{config.clusterConfigurationId}
</ProDescriptions.Item>
<ProDescriptions.Item label="预览结果" >
{config.useResult?'启用':'禁用'}
<ProDescriptions.Item label="预览结果">
{config.useResult ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item label="打印流" >
{config.useChangeLog?'启用':'禁用'}
<ProDescriptions.Item label="打印流">
{config.useChangeLog ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item label="最大行数">
{config.maxRowNum}
</ProDescriptions.Item>
<ProDescriptions.Item label="自动停止" >
{config.useAutoCancel?'启用':'禁用'}
<ProDescriptions.Item label="自动停止">
{config.useAutoCancel ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item span={2} label="JobManagerAddress">
{row.jobManagerAddress}
......@@ -325,10 +330,10 @@ const StudioHistory = (props: any) => {
{config.jobName}
</ProDescriptions.Item>
<ProDescriptions.Item label="片段机制">
{config.useSqlFragment?'启用':'禁用'}
{config.useSqlFragment ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item label="语句集">
{config.useStatementSet?'启用':'禁用'}
{config.useStatementSet ? '启用' : '禁用'}
</ProDescriptions.Item>
<ProDescriptions.Item label="并行度">
{config.parallelism}
......@@ -344,59 +349,57 @@ const StudioHistory = (props: any) => {
</ProDescriptions.Item>
</ProDescriptions>
)}
{type==2 && (
{type == 2 && (
<ProDescriptions
column={1}
title='FlinkSql 语句'
>
<ProDescriptions.Item label="JobId" >
<ProDescriptions.Item label="JobId">
<Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId}
<FireOutlined/> {row.jobId}
</Tag>
</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>
)}
{type==3 && (
{type == 3 && (
<ProDescriptions
column={2}
title='数据预览'
>
<ProDescriptions.Item span={2} label="JobId" >
<ProDescriptions.Item span={2} label="JobId">
<Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId}
<FireOutlined/> {row.jobId}
</Tag>
</ProDescriptions.Item>
<ProDescriptions.Item span={2} >
<ProDescriptions.Item span={2}>
<StudioPreview result={result} style={{width: '100%'}}/>
</ProDescriptions.Item>
</ProDescriptions>
)}
{type==4 && (
{type == 4 && (
<ProDescriptions
column={1}
title='异常信息'
>
<ProDescriptions.Item label="JobId" >
<ProDescriptions.Item label="JobId">
<Tag color="blue" key={row.jobId}>
<FireOutlined /> {row.jobId}
<FireOutlined/> {row.jobId}
</Tag>
</ProDescriptions.Item>
<ProDescriptions.Item>
<Scrollbars style={{height: '400px',width:'100%'}}>
<pre className={styles.code}>{row.error}</pre>
</Scrollbars>
<CodeShow height={"80vh"} language={"java"} code={row.error} theme={"vs-dark"}/>
</ProDescriptions.Item>
</ProDescriptions>
)}
</ModalForm>
</Modal>
</>
);
};
export default connect(({Studio}: {Studio: StateType}) => ({
export default connect(({Studio}: { Studio: StateType }) => ({
current: Studio.current,
refs: Studio.refs,
}))(StudioHistory);
......@@ -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 {connect} from "umi";
import {FireOutlined, ScheduleOutlined} from '@ant-design/icons';
import StudioSqlConfig from "@/components/Studio/StudioRightTool/StudioSqlConfig";
import {DIALECT, isSql} from "@/components/Studio/conf";
import {FireOutlined, ZoomInOutlined} from '@ant-design/icons';
import {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 [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 = () => {
return (<>
<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.success) ? <><Badge status="error"/><Text type="danger">Error</Text></> :
......@@ -55,17 +71,34 @@ const StudioMsg = (props:any) => {
</Link> <Divider type="vertical"/>{current.console.result.startTime}
<Divider type="vertical"/>{current.console.result.endTime}
<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></>}
<Divider type="vertical"/>
{current.console.result.jobConfig?.jobName&&<Text code>{current.console.result.jobConfig?.jobName}</Text>}
{current.console.result.jobId&&
{current.console.result.jobConfig?.jobName && <Text code>{current.console.result.jobConfig?.jobName}</Text>}
{current.console.result.jobId &&
(<>
<Divider type="vertical"/>
<Tag color="blue" key={current.console.result.jobId}>
<FireOutlined /> {current.console.result.jobId}
<FireOutlined/> {current.console.result.jobId}
</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>
{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>)}
......@@ -75,14 +108,38 @@ const StudioMsg = (props:any) => {
return (
<Typography>
{current?.task&&current.console.result.startTime?(isSql(current.task.dialect) ? renderCommonSqlContent():
renderFlinkSqlContent() ):<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
}
</Typography>
<>
<Typography>
{current?.task && current.console.result.startTime ? (isSql(current.task.dialect) ? renderCommonSqlContent() :
renderFlinkSqlContent()) : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>
}
</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,
}))(StudioMsg);
......@@ -30,25 +30,25 @@ import Tables from "@/pages/DataBase/Tables";
import {TreeDataNode} from "@/components/Studio/StudioTree/Function";
import Generation from "@/pages/DataBase/Generation";
const { DirectoryTree } = Tree;
const {DirectoryTree} = Tree;
const {Option} = Select;
const { TabPane } = Tabs;
const {TabPane} = Tabs;
const StudioMetaData = (props: any) => {
const {database,toolHeight, dispatch} = props;
const {database, toolHeight, dispatch} = props;
const [databaseId, setDatabaseId] = useState<number>();
const [treeData, setTreeData] = useState<[]>([]);
const [modalVisit, setModalVisit] = useState(false);
const [row, setRow] = useState<TreeDataNode>();
const onRefreshTreeData = (databaseId: number)=>{
if(!databaseId)return;
const onRefreshTreeData = (databaseId: number) => {
if (!databaseId) return;
setDatabaseId(databaseId);
const res = showMetaDataTable(databaseId);
res.then((result) => {
let tables = result.datas;
if(tables) {
if (tables) {
for (let i = 0; i < tables.length; i++) {
tables[i].title = tables[i].name;
tables[i].key = tables[i].name;
......@@ -64,26 +64,27 @@ const StudioMetaData = (props: any) => {
}
}
setTreeData(tables);
}else{
} else {
setTreeData([]);
}
});
};
const onChangeDataBase = (value: number)=>{
const onChangeDataBase = (value: number) => {
onRefreshTreeData(value);
};
const getDataBaseOptions = ()=>{
return <>{database.map(({ id, name, alias, type, enabled }) => (
<Option value={id} label={<><Tag color={enabled ? "processing" : "error"}>{type}</Tag>{ alias === "" ? name:alias}</>}>
<Tag color={enabled ? "processing" : "error"}>{type}</Tag>{ alias === "" ? name:alias}
const getDataBaseOptions = () => {
return <>{database.map(({id, name, alias, type, enabled}) => (
<Option value={id}
label={<><Tag color={enabled ? "processing" : "error"}>{type}</Tag>{alias === "" ? name : alias}</>}>
<Tag color={enabled ? "processing" : "error"}>{type}</Tag>{alias === "" ? name : alias}
</Option>
))}</>
};
const openColumnInfo = (e: React.MouseEvent, node: TreeDataNode) => {
if(node.isLeaf){
if (node.isLeaf) {
setRow(node);
setModalVisit(true);
}
......@@ -105,21 +106,21 @@ const StudioMetaData = (props: any) => {
{getDataBaseOptions()}
</Select>
<Scrollbars style={{height: (toolHeight - 32)}}>
{treeData.length>0?(
{treeData.length > 0 ? (
<DirectoryTree
showIcon
switcherIcon={<DownOutlined/>}
treeData={treeData}
onRightClick={({event, node}: any) => {
openColumnInfo(event, node)
}}
/>):(<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />)}
showIcon
switcherIcon={<DownOutlined/>}
treeData={treeData}
onRightClick={({event, node}: any) => {
openColumnInfo(event, node)
}}
/>) : (<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>)}
</Scrollbars>
<Modal
title={row?.key}
visible={modalVisit}
width={1000}
onCancel={()=>{
onCancel={() => {
cancelHandle();
}}
footer={[
......@@ -134,38 +135,40 @@ const StudioMetaData = (props: any) => {
<TabPane
tab={
<span>
<TableOutlined />
<TableOutlined/>
表信息
</span>
}
key="tableInfo"
>
{row?<Tables table={row}/>:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
{row ? <Tables table={row}/> : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
</TabPane>
<TabPane
tab={
<span>
<CodepenOutlined />
<CodepenOutlined/>
字段信息
</span>
}
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
tab={
<span>
<OrderedListOutlined />
<OrderedListOutlined/>
SQL 生成
</span>
}
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>
</Tabs>
</Modal>
</Modal>
</>
);
};
......
......@@ -18,8 +18,8 @@
*/
import {message, Tabs, Menu, Dropdown} from 'antd';
import React, {useState} from 'react';
import {Dropdown, Menu, message, Tabs} from 'antd';
import React from 'react';
import {connect} from 'umi';
import {StateType} from '@/pages/DataStudio/model';
import styles from './index.less';
......@@ -31,7 +31,7 @@ import {Dispatch} from "@@/plugin-dva/connect";
const {TabPane} = Tabs;
const EditorTabs = (props: any) => {
const {tabs, current, toolHeight, width,height} = props;
const {tabs, current, toolHeight, width, height} = props;
const onChange = (activeKey: any) => {
props.saveToolHeight(toolHeight);
......@@ -42,7 +42,7 @@ const EditorTabs = (props: any) => {
if (action === 'add') {
add();
} else if (action === 'remove') {
props.saveToolHeight(toolHeight-0.0001);
props.saveToolHeight(toolHeight - 0.0001);
// if (current.isModified) {
// saveTask(current, dispatch);
// }
......@@ -71,11 +71,11 @@ const EditorTabs = (props: any) => {
newActiveKey = newPanes[0].key;
}
}
props.saveTabs(newPanes,newActiveKey);
props.saveTabs(newPanes, newActiveKey);
};
const handleClickMenu = (e: any, current) => {
props.closeTabs(current,e.key);
props.closeTabs(current, e.key);
};
const menu = (pane) => (
......@@ -92,11 +92,11 @@ const EditorTabs = (props: any) => {
const Tab = (pane: any) => (
<span>
{pane.key === 0 ? (
pane.title
<>{pane.icon} {pane.title}</>
) : (
<Dropdown overlay={menu(pane)} trigger={['contextMenu']}>
<span className="ant-dropdown-link">
{pane.title}
<>{pane.icon} {pane.title}</>
</span>
</Dropdown>
)}
......@@ -105,52 +105,52 @@ const EditorTabs = (props: any) => {
return (
<>
{tabs.panes.length === 0?<StudioHome width={width} />:
<Tabs
hideAdd
type="editable-card"
size="small"
onChange={onChange}
activeKey={tabs.activeKey + ''}
onEdit={onEdit}
className={styles['edit-tabs']}
style={{height: height?height:toolHeight}}
>
{tabs.panes.map((pane,i) => (
<TabPane tab={Tab(pane)} key={pane.key} closable={pane.closable}>
<StudioEdit
tabsKey={pane.key}
sql={pane.value}
monaco={pane.monaco}
// sqlMetaData={pane.sqlMetaData}
height={height?height:(toolHeight - 32)}
width={width}
language={current.task.dialect === DIALECT.JAVA ? 'java' : 'sql'}
/>
</TabPane>
))}
</Tabs>}
</>
{tabs.panes.length === 0 ? <StudioHome width={width}/> :
<Tabs
hideAdd
type="editable-card"
size="small"
onChange={onChange}
activeKey={tabs.activeKey + ''}
onEdit={onEdit}
className={styles['edit-tabs']}
style={{height: height ? height : toolHeight}}
>
{tabs.panes.map((pane, i) => (
<TabPane tab={Tab(pane)} key={pane.key} closable={pane.closable}>
<StudioEdit
tabsKey={pane.key}
sql={pane.value}
monaco={pane.monaco}
// sqlMetaData={pane.sqlMetaData}
height={height ? height : (toolHeight - 32)}
width={width}
language={current.task.dialect === DIALECT.JAVA ? 'java' : 'sql'}
/>
</TabPane>
))}
</Tabs>}
</>
);
};
const mapDispatchToProps = (dispatch: Dispatch)=>({
closeTabs:(current: any,key: string)=>dispatch({
const mapDispatchToProps = (dispatch: Dispatch) => ({
closeTabs: (current: any, key: string) => dispatch({
type: 'Studio/closeTabs',
payload: {
deleteType: key,
current
},
}),saveTabs:(newPanes: any,newActiveKey: number)=>dispatch({
}), saveTabs: (newPanes: any, newActiveKey: number) => dispatch({
type: 'Studio/saveTabs',
payload: {
activeKey: newActiveKey,
panes: newPanes,
},
}),saveToolHeight:(toolHeight: number)=>dispatch({
}), saveToolHeight: (toolHeight: number) => dispatch({
type: 'Studio/saveToolHeight',
payload: toolHeight - 0.0001,
}),changeActiveKey:(activeKey: number)=>dispatch({
}), changeActiveKey: (activeKey: number) => dispatch({
type: 'Studio/changeActiveKey',
payload: activeKey,
}),
......@@ -161,4 +161,4 @@ export default connect(({Studio}: { Studio: StateType }) => ({
sql: Studio.sql,
tabs: Studio.tabs,
toolHeight: Studio.toolHeight,
}),mapDispatchToProps)(EditorTabs);
}), mapDispatchToProps)(EditorTabs);
......@@ -18,8 +18,8 @@
*/
import React, {useEffect, useState} from 'react';
import {Form, Button, Input, Modal} from 'antd';
import React, {useState} from 'react';
import {Button, Form, Input, Modal} from 'antd';
import type {CatalogueTableListItem} from '../data.d';
......@@ -40,6 +40,7 @@ const formLayout = {
const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => {
const [formVals, setFormVals] = useState<Partial<CatalogueTableListItem>>({
id: props.values.id,
taskId: props.values.taskId,
name: props.values.name,
isLeaf: props.values.isLeaf,
parentId: props.values.parentId,
......@@ -100,6 +101,7 @@ const UpdateCatalogueForm: React.FC<UpdateFormProps> = (props) => {
form={form}
initialValues={{
id: formVals.id,
taskId: formVals.taskId,
name: formVals.name,
isLeaf: formVals.isLeaf,
parentId: formVals.parentId,
......
......@@ -20,6 +20,7 @@
export type CatalogueTableListItem = {
id: number,
taskId: number,
name: string,
isLeaf: string,
parentId: number,
......
......@@ -230,9 +230,10 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
result.then(result => {
let newTabs = tabs;
let newPane: any = {
title: <>{node!.icon} {node!.name}</>,
title: node!.name,
key: node!.taskId,
value: (result.datas.statement ? result.datas.statement : ''),
icon: node!.icon,
closable: true,
path: node!.path,
task: {
......@@ -310,6 +311,7 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
setActiveNode(node);
setCatalogueFormValues({
id: node?.id,
taskId: node?.taskId,
name: node?.name,
});
};
......@@ -656,13 +658,16 @@ const StudioTree: React.FC<StudioTreeProps> = (props) => {
handleUpdateCatalogueModalVisible(false);
setCatalogueFormValues({});
getTreeData();
dispatch({
type: "Studio/renameTab",
payload: {
key: value.id,
name: <>{activeNode.icon} {value.name}</>
},
});
if (value.taskId) {
dispatch({
type: "Studio/renameTab",
payload: {
key: value.taskId,
title: value.name,
icon: activeNode.icon
},
});
}
}
}}
onCancel={() => {
......
......@@ -19,12 +19,12 @@
import React from "react";
import {KeyOutlined, CheckSquareOutlined} from '@ant-design/icons';
import {CheckSquareOutlined, KeyOutlined} from '@ant-design/icons';
import DTable from "@/components/Common/DTable";
const Columns = (props: any) => {
const {dbId,table,schema} = props;
const {dbId, table, schema, scroll} = props;
const cols = [{
title: '序号',
......@@ -50,7 +50,7 @@ const Columns = (props: any) => {
dataIndex: 'keyFlag',
render: (_, record) => (
<>
{record.keyFlag?<KeyOutlined style={{ color:'#FAA100'}} />:undefined}
{record.keyFlag ? <KeyOutlined style={{color: '#FAA100'}}/> : undefined}
</>
),
filters: [
......@@ -64,12 +64,12 @@ const Columns = (props: any) => {
},
],
openSearch: 'dict',
},{
}, {
title: '自增',
dataIndex: 'autoIncrement',
render: (_, record) => (
<>
{record.autoIncrement?<CheckSquareOutlined style={{ color:'#1296db'}} />:undefined}
{record.autoIncrement ? <CheckSquareOutlined style={{color: '#1296db'}}/> : undefined}
</>
),
filters: [
......@@ -83,12 +83,12 @@ const Columns = (props: any) => {
},
],
openSearch: 'dict',
},{
}, {
title: '非空',
dataIndex: 'nullable',
render: (_, record) => (
<>
{!record.nullable?<CheckSquareOutlined style={{ color:'#1296db'}} />:undefined}
{!record.nullable ? <CheckSquareOutlined style={{color: '#1296db'}}/> : undefined}
</>
),
filters: [
......@@ -102,30 +102,31 @@ const Columns = (props: any) => {
},
],
openSearch: 'dict',
},{
}, {
title: '默认值',
dataIndex: 'defaultValue',
},{
}, {
title: '精度',
dataIndex: 'precision',
isString: false,
},{
}, {
title: '小数范围',
dataIndex: 'scale',
isString: false,
},{
}, {
title: '字符集',
dataIndex: 'characterSet',
},{
}, {
title: '排序规则',
dataIndex: 'collation',
},{
}, {
title: 'Java 类型',
dataIndex: 'javaType',
},]
return (
<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 @@
import type {Effect, Reducer} from "umi";
import {
handleAddOrUpdate
} from "@/components/Common/crud";
import {handleAddOrUpdate} from "@/components/Common/crud";
import type {SqlMetaData} from "@/components/Studio/StudioEvent/data";
export type ClusterType = {
......@@ -124,6 +122,7 @@ export type TabsItemType = {
title: string;
key: number,
value: string;
icon: any;
closable: boolean;
path: string[];
task?: TaskType;
......@@ -629,19 +628,23 @@ const Model: ModelType = {
let newCurrent = state.current;
for (let i = 0; i < newTabs.panes.length; i++) {
if (newTabs.panes[i].key == payload.key) {
newTabs.panes[i].title = payload.name;
newTabs.panes[i].task.alias = payload.name;
newTabs.panes[i].title = payload.title;
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) {
newCurrent.title = payload.name;
newCurrent.task.alias = payload.name;
newCurrent.title = payload.title;
newCurrent.icon = payload.icon;
newCurrent.task.alias = payload.title;
newCurrent.path[newCurrent.path.length - 1] = payload.title;
}
}
if (newTabs.panes.length == 0) {
return {
...state,
current: undefined,
tabs: newTabs,
tabs: {...newTabs},
currentPath: ['引导页'],
};
}
......
......@@ -1630,6 +1630,24 @@ export default (): React.ReactNode => {
<li>
<Link>优化部署文档</Link>
</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>
</Paragraph>
</Timeline.Item>
......
......@@ -81,6 +81,7 @@ WITH 参数通常用于指定 CDCSOURCE 所需参数,语法为`'key1'='value1'
| sink.table.upper | 否 | 无 | 目标表的表名全大写 |
| sink.table.lower | 否 | 无 | 目标表的表名全小写 |
| sink.* | 否 | 无 | 目标数据源的配置信息,同 FlinkSQL,使用 ${schemaName} 和 ${tableName} 可注入经过处理的源表名 |
| sink[N].* | 否 | 无 | N代表为多目的地写入, 默认从0开始到N, 其他配置参数信息参考sink.*的配置. |
## 示例
......@@ -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 说明
......@@ -235,4 +265,4 @@ EXECUTE CDCSOURCE demo WITH (
- 禁用全局变量、语句集、批模式。
- 目前不支持 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