Skip to content

Commit bea6ac1

Browse files
committed
Merge branch 'main' of https://github.com/dataease/SQLBot
2 parents 556c2f8 + 80ec270 commit bea6ac1

File tree

14 files changed

+140
-55
lines changed

14 files changed

+140
-55
lines changed

backend/apps/chat/task/llm.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from langchain_community.utilities import SQLDatabase
1616
from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, AIMessage, BaseMessageChunk
1717
from sqlalchemy import select
18-
from sqlalchemy.exc import DBAPIError
1918
from sqlalchemy.orm import sessionmaker
2019
from sqlmodel import create_engine, Session
2120

@@ -30,13 +29,13 @@
3029
from apps.datasource.crud.datasource import get_table_schema
3130
from apps.datasource.crud.permission import get_row_permission_filters, is_normal_user
3231
from apps.datasource.models.datasource import CoreDatasource
33-
from apps.db.db import exec_sql, get_version
32+
from apps.db.db import exec_sql, get_version, check_connection
3433
from apps.system.crud.assistant import AssistantOutDs, AssistantOutDsFactory, get_assistant_ds
3534
from apps.system.schemas.system_schema import AssistantOutDsSchema
3635
from apps.terminology.curd.terminology import get_terminology_template
3736
from common.core.config import settings
3837
from common.core.deps import CurrentAssistant, CurrentUser
39-
from common.error import SingleMessageError
38+
from common.error import SingleMessageError, SQLBotDBError, ParseSQLResultError, SQLBotDBConnectionError
4039
from common.utils.utils import SQLBotLogUtil, extract_nested_json, prepare_for_orjson
4140

4241
warnings.filterwarnings("ignore")
@@ -868,7 +867,14 @@ def execute_sql(self, sql: str):
868867
Query results
869868
"""
870869
SQLBotLogUtil.info(f"Executing SQL on ds_id {self.ds.id}: {sql}")
871-
return exec_sql(self.ds, sql)
870+
try:
871+
return exec_sql(self.ds, sql)
872+
except Exception as e:
873+
if isinstance(e, ParseSQLResultError):
874+
raise e
875+
else:
876+
err = traceback.format_exc(limit=1, chain=True)
877+
raise SQLBotDBError(err)
872878

873879
def pop_chunk(self):
874880
try:
@@ -940,6 +946,12 @@ def run_task(self, in_chat: bool = True):
940946
ds=self.ds)
941947
else:
942948
self.validate_history_ds()
949+
950+
# check connection
951+
connected = check_connection(ds=self.ds, trans=None)
952+
if not connected:
953+
raise SQLBotDBConnectionError('Connect DB failed')
954+
943955
# generate sql
944956
sql_res = self.generate_sql()
945957
full_sql_text = ''
@@ -1059,13 +1071,12 @@ def run_task(self, in_chat: bool = True):
10591071
error_msg: str
10601072
if isinstance(e, SingleMessageError):
10611073
error_msg = str(e)
1062-
elif isinstance(e, ConnectionError):
1074+
elif isinstance(e, SQLBotDBConnectionError):
10631075
error_msg = orjson.dumps(
1064-
{'message': str(e), 'traceback': traceback.format_exc(limit=1),
1065-
'type': 'db-connection-err'}).decode()
1066-
elif isinstance(e, DBAPIError):
1076+
{'message': str(e), 'type': 'db-connection-err'}).decode()
1077+
elif isinstance(e, SQLBotDBError):
10671078
error_msg = orjson.dumps(
1068-
{'message': str(e), 'traceback': traceback.format_exc(limit=1), 'type': 'exec-sql-err'}).decode()
1079+
{'message': 'Execute SQL Failed', 'traceback': str(e), 'type': 'exec-sql-err'}).decode()
10691080
else:
10701081
error_msg = orjson.dumps({'message': str(e), 'traceback': traceback.format_exc(limit=1)}).decode()
10711082
self.save_error(message=error_msg)

backend/apps/db/db.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
import platform
44
import urllib.parse
55
from decimal import Decimal
6+
from typing import Optional
67

78
from apps.db.db_sql import get_table_sql, get_field_sql, get_version_sql
9+
from common.error import ParseSQLResultError
810

911
if platform.system() != "Darwin":
1012
import dmPython
@@ -100,7 +102,7 @@ def get_session(ds: CoreDatasource | AssistantOutDsSchema):
100102
return session
101103

102104

103-
def check_connection(trans: Trans, ds: CoreDatasource, is_raise: bool = False):
105+
def check_connection(trans: Optional[Trans], ds: CoreDatasource, is_raise: bool = False):
104106
db = DB.get_db(ds.type)
105107
if db.connect_type == ConnectType.sqlalchemy:
106108
conn = get_engine(ds, 10)
@@ -209,7 +211,8 @@ def get_schema(ds: CoreDatasource):
209211
if ds.type == "sqlServer":
210212
sql = f"""select name from sys.schemas"""
211213
elif ds.type == "pg" or ds.type == "excel":
212-
sql = """SELECT nspname FROM pg_namespace"""
214+
sql = """SELECT nspname
215+
FROM pg_namespace"""
213216
elif ds.type == "oracle":
214217
sql = f"""select * from all_users"""
215218
with session.execute(text(sql)) as result:
@@ -325,7 +328,7 @@ def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column=
325328
return {"fields": columns, "data": result_list,
326329
"sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
327330
except Exception as ex:
328-
raise ex
331+
raise ParseSQLResultError(str(ex))
329332
else:
330333
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration)))
331334
if ds.type == 'dm':
@@ -345,7 +348,7 @@ def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column=
345348
return {"fields": columns, "data": result_list,
346349
"sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
347350
except Exception as ex:
348-
raise ex
351+
raise ParseSQLResultError(str(ex))
349352
elif ds.type == 'doris':
350353
with pymysql.connect(user=conf.username, passwd=conf.password, host=conf.host,
351354
port=conf.port, db=conf.database, connect_timeout=conf.timeout,
@@ -364,7 +367,7 @@ def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column=
364367
return {"fields": columns, "data": result_list,
365368
"sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
366369
except Exception as ex:
367-
raise ex
370+
raise ParseSQLResultError(str(ex))
368371
elif ds.type == 'redshift':
369372
with redshift_connector.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username,
370373
password=conf.password,
@@ -383,4 +386,4 @@ def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column=
383386
return {"fields": columns, "data": result_list,
384387
"sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))}
385388
except Exception as ex:
386-
raise ex
389+
raise ParseSQLResultError(str(ex))

backend/common/error.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,16 @@ def __init__(self, message):
44
self.message = message
55

66
def __str__(self):
7-
return self.message
7+
return self.message
8+
9+
10+
class SQLBotDBConnectionError(Exception):
11+
pass
12+
13+
14+
class SQLBotDBError(Exception):
15+
pass
16+
17+
18+
class ParseSQLResultError(Exception):
19+
pass

frontend/src/views/chat/ErrorInfo.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const showBlock = computed(() => {
1717
})
1818
1919
const errorMessage = computed(() => {
20-
const obj = { message: props.error, showMore: false, traceback: '', type: '' }
20+
const obj = { message: props.error, showMore: false, traceback: '', type: undefined }
2121
if (showBlock.value && props.error?.trim().startsWith('{') && props.error?.trim().endsWith('}')) {
2222
try {
2323
const json = JSON.parse(props.error?.trim())
@@ -44,7 +44,7 @@ function showTraceBack() {
4444
<template>
4545
<div v-if="showBlock">
4646
<div
47-
v-if="!errorMessage.showMore"
47+
v-if="!errorMessage.showMore && errorMessage.type == undefined"
4848
v-dompurify-html="errorMessage.message"
4949
class="error-container"
5050
></div>
@@ -58,7 +58,9 @@ function showTraceBack() {
5858
<template v-else>
5959
{{ t('chat.error') }}
6060
</template>
61-
<el-button text @click="showTraceBack">{{ t('chat.show_error_detail') }}</el-button>
61+
<el-button v-if="errorMessage.showMore" text @click="showTraceBack">
62+
{{ t('chat.show_error_detail') }}
63+
</el-button>
6264
</div>
6365

6466
<el-drawer

frontend/src/views/chat/chat-block/ChartBlock.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ function addToDashboard() {
216216
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
217217
const chartBaseInfo = JSON.parse(props.message?.record?.chart)
218218
recordeInfo['chart'] = {
219-
type: chartBaseInfo.type,
219+
type: currentChartType.value,
220220
title: chartBaseInfo.title,
221221
columns: chartBaseInfo.columns,
222222
xAxis: chartBaseInfo.axis?.x ? [chartBaseInfo.axis.x] : [],
@@ -395,7 +395,7 @@ watch(
395395
</div>
396396
</el-popover>
397397
</div>
398-
<div v-if="message?.record?.chart">
398+
<div v-if="message?.record?.chart && isCompletePage">
399399
<el-tooltip effect="dark" :content="t('chat.add_to_dashboard')" placement="top">
400400
<el-button class="tool-btn" text @click="addToDashboard">
401401
<el-icon size="16">

frontend/src/views/dashboard/canvas/CanvasCore.vue

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,8 @@ const forceComputed = () => {
10631063
}
10641064
10651065
function addItemBox(item: CanvasItem) {
1066+
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
1067+
item.x = findPositionX(item)
10661068
canvasComponentData.value.push(item)
10671069
forceComputed()
10681070
nextTick(() => {
@@ -1166,6 +1168,49 @@ function tabMoveInCheckSQ() {
11661168
}
11671169
}
11681170
1171+
/**
1172+
* Find position box
1173+
*/
1174+
function findPositionX(item: CanvasItem) {
1175+
const width = item.sizeX
1176+
let resultX = 1
1177+
let checkPointYIndex = -1 // -1 means not occupying any Y-direction canvas
1178+
// Component width
1179+
let pb = positionBox.value
1180+
if (width <= 0) return
1181+
// Find the highest position index of the component. Component rule: the latest y is 1.
1182+
canvasComponentData.value.forEach((component) => {
1183+
const componentYIndex = component.y + component.sizeY - 2
1184+
if (checkPointYIndex < componentYIndex) {
1185+
checkPointYIndex = componentYIndex
1186+
}
1187+
})
1188+
// Start checking from index i in the X direction;
1189+
const pbX = pb[checkPointYIndex]
1190+
// Get the last column array in the X direction
1191+
if (checkPointYIndex < 0 || !pbX) {
1192+
return 1
1193+
} else {
1194+
// The width to check is the component width. The end index of the check is checkEndIndex = i + width - 1;
1195+
// The exit condition for the check is when the end index checkEndIndex is out of bounds (exceeds the end index of pbX).
1196+
for (let i = 0, checkEndIndex = width - 1; checkEndIndex < pbX.length; i++, checkEndIndex++) {
1197+
let adaptorCount = 0
1198+
// Locate the occupied position in the last column
1199+
for (let k = 0; k < width; k++) {
1200+
// pbX[i + k].el === false indicates that the current matrix point is not occupied. When the width of consecutive unoccupied matrix points equals the component width, the starting point i is available.
1201+
if (!pbX[i + k].el) {
1202+
adaptorCount++
1203+
}
1204+
}
1205+
if (adaptorCount === width) {
1206+
resultX = i + 1
1207+
break
1208+
}
1209+
}
1210+
return resultX
1211+
}
1212+
}
1213+
11691214
useEmitt({
11701215
name: `editor-delete-${props.canvasId}`,
11711216
callback: removeItemById,

frontend/src/views/dashboard/common/AddViewDashboard.vue

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const optInit = (viewInfo: any) => {
2020
resourceDialogShow.value = true
2121
state.viewInfo = viewInfo
2222
}
23-
const curOptDashboardId = ref(null)
2423
const state = reactive({
2524
dashboardList: [] as SQTreeNode[],
2625
viewInfo: null,
@@ -127,20 +126,19 @@ const saveResourcePrepare = () => {
127126
const saveResource = (params: any, commonParams: any) => {
128127
saveDashboardResourceTarget(params, commonParams, (res: any) => {
129128
const messageTips = t('dashboard.add_success')
130-
curOptDashboardId.value = res?.id
131-
openMessageLoading(messageTips, 'success', callbackExportSuc)
129+
openMessageLoading(messageTips, 'success', res?.id, callbackExportSuc)
132130
resetForm()
133131
})
134132
}
135133
136-
const callbackExportSuc = () => {
134+
const callbackExportSuc = (curOptDashboardIdValue: any) => {
137135
// do open dashboard
138-
const url = `#/canvas?resourceId=${curOptDashboardId.value}`
136+
const url = `#/canvas?resourceId=${curOptDashboardIdValue}`
139137
window.open(url, '_self')
140138
}
141139
142140
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
143-
const openMessageLoading = (text: string, type = 'success', cb: Function) => {
141+
const openMessageLoading = (text: string, type = 'success', dvId: any, cb: Function) => {
144142
// success error loading
145143
const customClass = `sq-message-${type || 'success'} sq-message-export`
146144
ElMessage({
@@ -161,15 +159,15 @@ const openMessageLoading = (text: string, type = 'success', cb: Function) => {
161159
size: 'small',
162160
class: 'btn-text',
163161
onClick: () => {
164-
cb()
162+
cb(dvId)
165163
},
166164
},
167165
t('dashboard.open_dashboard')
168166
),
169167
]),
170168
type,
171169
showClose: true,
172-
duration: 0,
170+
duration: 2000,
173171
customClass,
174172
})
175173
}

frontend/src/views/dashboard/editor/Toolbar.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ const editCanvasName = () => {
9191
nameInput.value?.focus()
9292
})
9393
}
94+
const handleEnterEditCanvasName = (event: Event) => {
95+
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
96+
event.target?.blur()
97+
}
9498
const closeEditCanvasName = () => {
9599
nameEdit.value = false
96100
if (!inputName.value || !inputName.value.trim()) {
@@ -240,6 +244,7 @@ const previewInner = () => {
240244
<input
241245
ref="nameInput"
242246
v-model="inputName"
247+
@keydown.enter="handleEnterEditCanvasName"
243248
@change="onDvNameChange"
244249
@blur="closeEditCanvasName"
245250
/>

frontend/src/views/dashboard/editor/index.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,23 @@ const addComponent = (componentType: string, viewInfo?: any) => {
4040
component.propValue[0].title = t('dashboard.new_tab')
4141
component.activeTabName = subTabName
4242
}
43-
component.y = 100
43+
component.y = maxYComponentCount() + 10
4444
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
4545
dashboardEditorInnerRef.value.addItemToBox(component)
4646
}
4747
}
4848
49+
const maxYComponentCount = () => {
50+
if (componentData.value.length === 0) {
51+
return 1
52+
} else {
53+
return componentData.value
54+
.filter((item) => item['y'])
55+
.map((item) => item['y'] + item['sizeY']) // Calculate the y+sizeY of each element
56+
.reduce((max, current) => Math.max(max, current), 0)
57+
}
58+
}
59+
4960
onMounted(() => {
5061
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
5162
state.opt = router.currentRoute.value.query.opt

frontend/src/views/system/embedded/Page.vue

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,24 @@ const search = () => {
199199
}
200200
201201
const termFormRef = ref()
202-
202+
const validateUrl = (_: any, value: any, callback: any) => {
203+
if (value === '') {
204+
callback(
205+
new Error(
206+
t('datasource.please_enter') + t('common.empty') + t('embedded.cross_domain_settings')
207+
)
208+
)
209+
} else {
210+
// var Expression = /(https?:\/\/)?([\da-z\.-]+)\.([a-z]{2,6})(:\d{1,5})?([\/\w\.-]*)*\/?(#[\S]+)?/ // eslint-disable-line
211+
var Expression = /^https?:\/\/[^\s/?#]+(:\d+)?/i
212+
var objExp = new RegExp(Expression)
213+
if (objExp.test(value) && !value.endsWith('/')) {
214+
callback()
215+
} else {
216+
callback(t('embedded.format_is_incorrect'))
217+
}
218+
}
219+
}
203220
const rules = {
204221
name: [
205222
{
@@ -210,8 +227,8 @@ const rules = {
210227
domain: [
211228
{
212229
required: true,
213-
message:
214-
t('datasource.please_enter') + t('common.empty') + t('embedded.cross_domain_settings'),
230+
validator: validateUrl,
231+
trigger: 'blur',
215232
},
216233
],
217234
}

0 commit comments

Comments
 (0)