(feature)(chat-sdk) add export error msg and export data (#1803)

This commit is contained in:
williamhliu
2024-10-15 10:24:27 +08:00
committed by GitHub
parent d7f301064a
commit 2ee77e39ff
7 changed files with 109 additions and 29 deletions

View File

@@ -148,6 +148,7 @@ export type MsgDataType = {
similarQueries: SimilarQuestionType[];
recommendedDimensions: DrillDownDimensionType[];
textResult: string;
errorMsg: string;
};
export enum ParseStateEnum {

View File

@@ -1,11 +1,12 @@
import { Spin, Row, Col, Switch } from 'antd';
import { CheckCircleFilled } from '@ant-design/icons';
import { Spin, Switch, Button } from 'antd';
import { CheckCircleFilled, DownloadOutlined } from '@ant-design/icons';
import { PREFIX_CLS, MsgContentTypeEnum } from '../../common/constants';
import { MsgDataType } from '../../common/type';
import ChatMsg from '../ChatMsg';
import WebPage from '../ChatMsg/WebPage';
import Loading from './Loading';
import React, { ReactNode, useState } from 'react';
import { exportCsvFile } from '../../utils/utils';
type Props = {
queryId?: number;
@@ -59,6 +60,20 @@ const ExecuteItem: React.FC<Props> = ({
);
};
const onExportData = () => {
const { queryColumns, queryResults } = data || {};
if (!!queryResults) {
const exportData = queryResults.map(item => {
return Object.keys(item).reduce((result, key) => {
const columnName = queryColumns?.find(column => column.nameEn === key)?.name || key;
result[columnName] = item[key];
return result;
}, {});
});
exportCsvFile(exportData);
}
};
if (executeLoading) {
return getNodeTip(`${titlePrefix}查询中`);
}
@@ -67,7 +82,7 @@ const ExecuteItem: React.FC<Props> = ({
return getNodeTip(
<>
{titlePrefix}
{data?.queryTimeCost && isDeveloper && (
{!!data?.queryTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {data.queryTimeCost}ms)</span>
)}
</>,
@@ -83,28 +98,35 @@ const ExecuteItem: React.FC<Props> = ({
<>
<div className={`${prefixCls}-title-bar`}>
<CheckCircleFilled className={`${prefixCls}-step-icon`} />
<div className={`${prefixCls}-step-title`} style={{ width: '100%' }}>
<Row style={{ width: '100%' }}>
<Col flex="1 1 auto">
{titlePrefix}
{data?.queryTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {data.queryTimeCost}ms)</span>
)}
</Col>
<Col flex="0 1 70px">
{[MsgContentTypeEnum.METRIC_TREND, MsgContentTypeEnum.METRIC_BAR].includes(
msgContentType as MsgContentTypeEnum
) && (
<Switch
checkedChildren="表格"
unCheckedChildren="表格"
onChange={checked => {
setShowMsgContentTable(checked);
}}
/>
)}
</Col>
</Row>
<div
className={`${prefixCls}-step-title ${prefixCls}-execute-title-bar`}
style={{ width: '100%' }}
>
<div>
{titlePrefix}
{!!data?.queryTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {data.queryTimeCost}ms)</span>
)}
</div>
<div>
{[MsgContentTypeEnum.METRIC_TREND, MsgContentTypeEnum.METRIC_BAR].includes(
msgContentType as MsgContentTypeEnum
) && (
<Switch
checkedChildren="表格"
unCheckedChildren="表格"
onChange={checked => {
setShowMsgContentTable(checked);
}}
/>
)}
{!!data?.queryColumns?.length && (
<Button className={`${prefixCls}-export-data`} size="small" onClick={onExportData}>
<DownloadOutlined />
</Button>
)}
</div>
</div>
</div>
<div

View File

@@ -118,7 +118,7 @@ const ParseTip: React.FC<Props> = ({
return getNode(
<>
{parseTimeCost && isDeveloper && (
{!!parseTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {parseTimeCost}ms)</span>
)}
</>,
@@ -338,7 +338,7 @@ const ParseTip: React.FC<Props> = ({
<div className={`${prefixCls}-title-bar`}>
<div>
{parseTimeCost && isDeveloper && (
{!!parseTimeCost && isDeveloper && (
<span className={`${prefixCls}-title-tip`}>(: {parseTimeCost}ms)</span>
)}
{parseInfoOptions?.length > 1 ? '' : ''}

View File

@@ -19,6 +19,7 @@ type Props = {
queryMode?: string;
sqlInfo: SqlInfoType;
sqlTimeCost?: number;
executeErrorMsg: string;
};
const SqlItem: React.FC<Props> = ({
@@ -31,6 +32,7 @@ const SqlItem: React.FC<Props> = ({
queryMode,
sqlInfo,
sqlTimeCost,
executeErrorMsg,
}) => {
const [sqlType, setSqlType] = useState('');
@@ -126,6 +128,14 @@ ${format(sqlInfo.querySQL)}
`;
};
const getErrorMsgText = () => {
return `
异常日志
${executeErrorMsg}
`;
};
const onExportLog = () => {
let text = '';
if (question) {
@@ -148,6 +158,9 @@ ${format(sqlInfo.querySQL)}
if (sqlInfo.querySQL) {
text += getQuerySQLText();
}
if (!!executeErrorMsg) {
text += getErrorMsgText();
}
exportTextFile(text, `supersonic-debug-${agentId}-${queryId}.log`);
};
@@ -157,7 +170,7 @@ ${format(sqlInfo.querySQL)}
<CheckCircleFilled className={`${tipPrefixCls}-step-icon`} />
<div className={`${tipPrefixCls}-step-title`}>
SQL生成
{sqlTimeCost && (
{!!sqlTimeCost && (
<span className={`${tipPrefixCls}-title-tip`}>(: {sqlTimeCost}ms)</span>
)}

View File

@@ -73,6 +73,7 @@ const ChatItem: React.FC<Props> = ({
const [executeMode, setExecuteMode] = useState(false);
const [executeLoading, setExecuteLoading] = useState(false);
const [executeTip, setExecuteTip] = useState('');
const [executeErrorMsg, setExecuteErrorMsg] = useState('');
const [data, setData] = useState<MsgDataType>();
const [entitySwitchLoading, setEntitySwitchLoading] = useState(false);
const [dimensionFilters, setDimensionFilters] = useState<FilterItemType[]>([]);
@@ -87,8 +88,9 @@ const ChatItem: React.FC<Props> = ({
const updateData = (res: Result<MsgDataType>) => {
let tip: string = '';
let data: MsgDataType | undefined = undefined;
const { queryColumns, queryResults, queryState, queryMode, response, chatContext } =
const { queryColumns, queryResults, queryState, queryMode, response, chatContext, errorMsg } =
res.data || {};
setExecuteErrorMsg(errorMsg);
if (res.code === 400 || res.code === 401 || res.code === 412) {
tip = res.msg;
} else if (res.code !== 200) {
@@ -366,6 +368,7 @@ const ChatItem: React.FC<Props> = ({
queryMode={parseInfo.queryMode}
sqlInfo={parseInfo.sqlInfo}
sqlTimeCost={parseTimeCost?.sqlTime}
executeErrorMsg={executeErrorMsg}
/>
)}
<ExecuteItem

View File

@@ -151,6 +151,12 @@
color: var(--text-color);
}
&-execute-title-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
&-reload {
margin-left: 2px;
width: fit-content;
@@ -392,6 +398,14 @@
border-left: 1px solid var(--green);
overflow: auto;
}
&-export-data {
margin-left: 12px;
width: fit-content;
font-weight: normal;
color: var(--text-color-secondary);
font-size: 13px !important;
}
}
.@{filter-item-prefix-cls} {

View File

@@ -303,3 +303,30 @@ export function exportTextFile(content: string, fileName: string, mimeType: stri
// 释放 URL 对象
URL.revokeObjectURL(url);
}
function replacer(key: string, value: any) {
return value === null ? '' : value; // 将null值转换为空字符串
}
export function exportCsvFile(data: any[]) {
// 生成CSV内容
const csvRows: any[] = [];
const headers = Object.keys(data[0]);
csvRows.push(headers.join(',')); // 添加表头
for (const row of data) {
csvRows.push(headers.map(header => JSON.stringify(row[header], replacer)).join(','));
}
// 创建Blob并下载文件
const csvString = '\ufeff' + csvRows.join('\n');
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'data.csv'; // 指定下载文件名
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url); // 释放Blob URL
}