Merge branch 'developing' into team

This commit is contained in:
JiaJu Zhuang
2023-07-30 16:04:13 +08:00
11 changed files with 141 additions and 71 deletions

View File

@ -1,3 +1,13 @@
# 2.0.7
## ⭐ New Features
- Export query result as file is supported
## 🐞 Bug Fixes
- Fixed ai config issues [Issue #346](https://github.com/chat2db/Chat2DB/issues/346)
# 2.0.6
## 🐞 Bug Fixes

View File

@ -195,15 +195,12 @@ export default function SettingAI(props: IProps) {
</div>
)}
<div className={styles.bottomButton}>
{/* <Button >
</Button> */}
<Button type="primary" onClick={handleApplyAiConfig}>
{i18n('setting.button.apply')}
</Button>
</div>
{aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && <Popularize source="setting" />}
{/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && <Popularize source="setting" />} */}
</>
);
}

View File

@ -9,12 +9,11 @@ import ProxySetting from './ProxySetting';
import About from './About';
import { connect } from 'umi';
import { IAIState } from '@/models/ai';
import styles from './index.less';
import configService from '@/service/config';
import { AiSqlSourceType } from '@/typings/ai';
import TestVersion from '@/components/TestVersion';
import { IAiConfig } from '@/typings';
import styles from './index.less';
interface IProps {
aiConfig: IAiConfig;
className?: string;
@ -28,6 +27,12 @@ function Setting(props: IProps) {
const [currentMenu, setCurrentMenu] = useState(0);
useEffect(() => {
if (isModalVisible) {
getAiSystemConfig();
}
}, [isModalVisible]);
useEffect(() => {
getAiSystemConfig();
}, []);

View File

@ -111,6 +111,7 @@
// top: 0;
// right: 0;
// bottom: 0;
cursor: pointer;
i {
font-size: 15px;
@ -139,3 +140,7 @@
}
}
}
.monacoEditor {
height: 60vh;
}

View File

@ -24,7 +24,7 @@ interface ITableProps {
config: IResultConfig;
onConfigChange: (config: IResultConfig) => void;
onSearchTotal: () => Promise<number | undefined>;
onExport: (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void;
onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void;
}
interface IViewTableCellData {
@ -54,42 +54,46 @@ export default function TableBox(props: ITableProps) {
const [appTheme] = useTheme();
const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]);
const handleExport = (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => {
props.onExport && props.onExport(data.sql, data.originalSql, exportType, exportSize);
};
const items: MenuProps['items'] = useMemo(
() => [
{
label: '导出全部数据为csv',
key: '1',
icon: <UserOutlined />,
// icon: <UserOutlined />,
onClick: () => {
props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL);
handleExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL);
},
},
{
label: '导出全部数据为插入语句',
key: '2',
icon: <UserOutlined />,
// icon: <UserOutlined />,
onClick: () => {
props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL);
handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL);
},
},
{
label: '导出当前页数据为csv',
key: '3',
icon: <UserOutlined />,
// icon: <UserOutlined />,
onClick: () => {
props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE);
handleExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE);
},
},
{
label: '导出当前页数据为插入语句',
key: '4',
icon: <UserOutlined />,
// icon: <UserOutlined />,
onClick: () => {
props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE);
handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE);
},
},
],
[],
[data],
);
const defaultSorts: SortItem[] = useMemo(
@ -260,6 +264,7 @@ export default function TableBox(props: ITableProps) {
open={!!viewTableCellData?.name}
onCancel={handleCancel}
width="60vw"
height="70vh"
maskClosable={false}
footer={
<>

View File

@ -1,4 +1,4 @@
import React, { memo, useEffect, useState, useMemo, Fragment } from 'react';
import React, { memo, useEffect, useState, useMemo, Fragment, useCallback } from 'react';
import classnames from 'classnames';
import Tabs, { IOption } from '@/components/Tabs';
import Iconfont from '@/components/Iconfont';
@ -16,7 +16,7 @@ interface IProps {
manageResultDataList?: IManageResultData[];
resultConfig: IResultConfig[];
onExecute: (sql: string, config: IResultConfig, index: number) => void;
onExport: (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise<void>;
onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise<void>;
onTabEdit: (type: 'add' | 'remove', value?: number | string) => void;
onSearchTotal: (index: number) => Promise<number>;
isLoading?: boolean;
@ -45,7 +45,7 @@ const handleTabs = (result: IManageResultData[]) => {
};
export default memo<IProps>(function SearchResult(props) {
const { className, manageResultDataList = [], isLoading, onExecute, onSearchTotal } = props;
const { className, manageResultDataList = [], isLoading, onExecute, onExport } = props;
const [currentTab, setCurrentTab] = useState<string | number | undefined>();
const [resultDataList, setResultDataList] = useState<IManageResultData[]>([]);
const [resultConfig, setResultConfig] = useState<IResultConfig[]>([]);
@ -64,7 +64,7 @@ export default memo<IProps>(function SearchResult(props) {
setCurrentTab(manageResultDataList[0].uuid);
}
setResultDataList(manageResultDataList);
setResultDataList([...manageResultDataList]);
setTabs(handleTabs(manageResultDataList));
}, [manageResultDataList]);
@ -85,48 +85,50 @@ export default memo<IProps>(function SearchResult(props) {
);
};
const renderTable = useMemo(() => {
const renderTable = () => {
if (!tabs || !tabs.length) {
return renderEmpty();
}
if (!resultDataList || !resultDataList.length) {
return renderEmpty();
}
return (resultDataList || []).map((item, index: number) => {
if (item.success) {
return (
<Fragment key={item.uuid!}>
<TableBox
className={classnames({ [styles.cursorTableBox]: item.uuid === currentTab })}
data={item}
config={resultConfig?.[index]}
onConfigChange={function (config: IResultConfig) {
onExecute && onExecute(item.originalSql, config, index);
}}
onSearchTotal={async () => {
if (props.onSearchTotal) {
return await props.onSearchTotal(index);
}
}}
onExport={(exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => {
props.onExport && props.onExport(item.originalSql, exportType, exportSize);
}}
/>
</Fragment>
);
} else {
return (
<Fragment key={item.uuid}>
<StateIndicator
className={classnames(styles.stateIndicator, { [styles.cursorStateIndicator]: item.uuid === currentTab })}
state="error"
text={item.message}
/>
</Fragment>
);
}
});
}, [currentTab, resultDataList, resultConfig]);
return renderTableContent;
};
const renderTableContent = (resultDataList || []).map((item, index: number) => {
console.log(item.sql, item.originalSql);
if (item.success) {
return (
<TableBox
key={item.uuid!}
className={classnames({ [styles.cursorTableBox]: item.uuid === currentTab })}
data={item}
config={resultConfig?.[index]}
onConfigChange={(config: IResultConfig) => {
onExecute && onExecute(item.originalSql, config, index);
}}
onSearchTotal={async () => {
if (props.onSearchTotal) {
return await props.onSearchTotal(index);
}
}}
onExport={(sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => {
onExport && onExport(sql, originalSql, exportType, exportSize);
}}
/>
);
} else {
return (
<StateIndicator
key={item.uuid}
className={classnames(styles.stateIndicator, { [styles.cursorStateIndicator]: item.uuid === currentTab })}
state="error"
text={item.message}
/>
);
}
});
return (
<div className={classnames(className, styles.box)}>
@ -144,7 +146,7 @@ export default memo<IProps>(function SearchResult(props) {
</div>
) : null}
<Spin spinning={isLoading} wrapperClassName={styles.resultContentWrapper}>
{renderTable}
{renderTable()}
</Spin>
</div>
);

View File

@ -15,6 +15,7 @@ import { v4 as uuidV4 } from 'uuid';
import sql from '@/service/sql';
import { isNumber } from 'lodash';
import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable';
import { downloadFile } from '@/utils/common';
interface IProps {
className?: string;
isActive: boolean;
@ -139,10 +140,14 @@ const WorkspaceRightItem = memo<IProps>(function (props) {
return total;
};
const handleExportSQLResult = async (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => {
const params: IExportParams = { ...data, originalSql, exportType, exportSize };
await sqlServer.exportResultTable(params);
const handleExportSQLResult = async (
sql: string,
originalSql: string,
exportType: ExportTypeEnum,
exportSize: ExportSizeEnum,
) => {
const params: IExportParams = { ...data, sql, originalSql, exportType, exportSize };
downloadFile(window._BaseURL + '/api/rdb/dml/export', params);
};
const handleResultTabEdit = (type: 'add' | 'remove', uuid?: string | number) => {

View File

@ -123,7 +123,7 @@ export interface IExportParams extends IExecuteSqlParams {
/**
* 导出-表格
*/
const exportResultTable = createRequest<IExportParams, any>('/api/rdb/dml/export', { method: 'post' });
// const exportResultTable = createRequest<IExportParams, any>('/api/rdb/dml/export', { method: 'post' });
export default {
getList,
@ -142,5 +142,5 @@ export default {
addTablePin,
deleteTablePin,
getDMLCount,
exportResultTable
// exportResultTable
};

View File

@ -14,3 +14,45 @@ export function formatParams(obj: { [key: string]: any }) {
});
return params.toString();
}
export function downloadFile(url: string, params: any) {
// 创建POST请求
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json', // 或者根据服务端的要求设置其他的内容类型
},
body: JSON.stringify(params), // 将参数转换为JSON字符串
})
.then((response) => {
// 从content-disposition头中获取文件名
const contentDisposition = response.headers.get('content-disposition');
const filename = contentDisposition ? decodeURIComponent(contentDisposition.split("''")[1]) : 'file.txt';
// 获取返回的Blob数据
return response.blob().then((blob) => ({ blob, filename }));
})
.then(({ blob, filename }) => {
// 创建一个代表Blob对象的URL
const blobUrl = URL.createObjectURL(blob);
// 创建一个隐藏的 <a> 标签,并设置其 href 属性
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
// 使用从响应头解析的文件名
a.download = filename;
// 将 <a> 标签附加到 DOM并触发点击事件
document.body.appendChild(a);
a.click();
// 清理:从 DOM 中移除 <a> 标签并释放Blob URL
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
})
.catch((error) => {
console.error('下载文件失败:', error);
});
}

View File

@ -4,7 +4,6 @@ import ai.chat2db.server.tools.common.model.ConfigJson;
import ai.chat2db.server.tools.common.util.ConfigUtils;
import com.dtflys.forest.springboot.annotation.ForestScan;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
@ -37,7 +36,7 @@ public class Application {
// Represents that the current version has been successfully launched
if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) {
// Flyway doesn't need to start every time to increase startup speed
args = ArrayUtils.add(args, "--spring.flyway.enabled=false");
//args = ArrayUtils.add(args, "--spring.flyway.enabled=false");
log.info("The current version {} has been successfully launched once and will no longer load Flyway.",
currentVersion);
}

View File

@ -37,13 +37,13 @@ public class MybatisGeneratorTest extends BaseTest {
private void doGenerator(List<String> tableList) {
// 当前项目地址 拿到的是ali-dbhub-server-start地址
// 当前项目地址 拿到的是chat2db-server-start地址
String outputDir = System.getProperty("user.dir")
+ "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main"
+ "/../chat2db-server-domain/chat2db-server-domain-repository/src/main"
+ "/java";
String xmlDir = System.getProperty("user.dir")
+ "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main"
+ "/resources/com/alibaba/dbhub/server/domain/repository";
+ "/../chat2db-server-domain/chat2db-server-domain-repository/src/main"
+ "/resources/ai/chat2db/server/domain/repository";
// 不要生成service controller
Map<OutputFile, String> pathInfo = new HashMap<>();
@ -58,7 +58,7 @@ public class MybatisGeneratorTest extends BaseTest {
//全局配置
.globalConfig(builder -> {
// 设置作者
builder.author("ali-dbhub")
builder.author("chat2db")
//执行完毕不打开文件夹
.disableOpenDir()
// 指定输出目录