fix: classification results empty and res.data access issues

Backend:
- Add GET /classifications/results endpoint with project/level/keyword filters
- Add column relationship to ClassificationResult model
- Fix test data generator to fetch column IDs from DB after bulk insert

Frontend:
- Fix request.ts interceptor to return full response body (keep total/pagination)
- Fix all pages to use res.data instead of res
- Add getClassificationResults API in classification.ts
- Implement fetchData in Classification.vue with proper filtering and pagination
- Fix same res.data issue in Category.vue, Metadata.vue, Project.vue, DataSource.vue, Dashboard.vue, Task.vue
This commit is contained in:
hiderfong
2026-04-23 10:46:51 +08:00
parent 4b08bb5057
commit 5119ca775b
9 changed files with 100 additions and 20 deletions
+48
View File
@@ -151,6 +151,54 @@ def list_templates(
return ResponseModel(data=[TemplateOut.model_validate(i) for i in items]) return ResponseModel(data=[TemplateOut.model_validate(i) for i in items])
@router.get("/results", response_model=ListResponse)
def list_results(
project_id: Optional[int] = Query(None),
level_id: Optional[int] = Query(None),
keyword: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=500),
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
from app.services.project_service import list_results as _list_results
items, total = _list_results(db, project_id=project_id, keyword=keyword, page=page, page_size=page_size)
data = []
for r in items:
col = r.column
table = col.table if col else None
database = table.database if table else None
source = database.source if database else None
# Filter by level_id if specified
if level_id and r.level_id != level_id:
continue
data.append({
"id": r.id,
"project_id": r.project_id,
"column_id": col.id if col else None,
"column_name": col.name if col else None,
"data_type": col.data_type if col else None,
"comment": col.comment if col else None,
"table_name": table.name if table else None,
"database_name": database.name if database else None,
"source_name": source.name if source else None,
"category_id": r.category_id,
"category_name": r.category.name if r.category else None,
"level_id": r.level_id,
"level_name": r.level.name if r.level else None,
"level_color": r.level.color if r.level else None,
"source": r.source,
"confidence": r.confidence,
"status": r.status,
"created_at": r.created_at.isoformat() if r.created_at else None,
})
return ListResponse(data=data, total=total, page=page, page_size=page_size)
@router.post("/auto-classify/{project_id}") @router.post("/auto-classify/{project_id}")
def auto_classify( def auto_classify(
project_id: int, project_id: int,
+1
View File
@@ -92,6 +92,7 @@ class ClassificationResult(Base):
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
project = relationship("ClassificationProject", back_populates="results") project = relationship("ClassificationProject", back_populates="results")
column = relationship("DataColumn")
category = relationship("Category") category = relationship("Category")
level = relationship("DataLevel") level = relationship("DataLevel")
+3 -1
View File
@@ -417,7 +417,9 @@ print(f" Created {len(projects)} projects")
# ============================================================ # ============================================================
print("Generating classification results...") print("Generating classification results...")
all_col_ids = [c.id for c in all_columns] # Re-fetch column IDs from DB since bulk_save_objects doesn't populate object IDs
col_rows = db.query(DataColumn.id).all()
all_col_ids = [c[0] for c in col_rows]
random.shuffle(all_col_ids) random.shuffle(all_col_ids)
result_batch = [] result_batch = []
+31
View File
@@ -76,3 +76,34 @@ export function deleteRule(id: number) {
export function getTemplates() { export function getTemplates() {
return request.get('/classifications/templates') return request.get('/classifications/templates')
} }
export interface ClassificationResultItem {
id: number
project_id: number
column_id?: number
column_name?: string
data_type?: string
comment?: string
table_name?: string
database_name?: string
source_name?: string
category_id?: number
category_name?: string
level_id?: number
level_name?: string
level_color?: string
source: string
confidence: number
status: string
created_at?: string
}
export function getClassificationResults(params: {
project_id?: number
level_id?: number
keyword?: string
page?: number
page_size?: number
}) {
return request.get('/classifications/results', { params })
}
+1 -1
View File
@@ -26,7 +26,7 @@ request.interceptors.response.use(
ElMessage.error(res.message || '请求失败') ElMessage.error(res.message || '请求失败')
return Promise.reject(new Error(res.message)) return Promise.reject(new Error(res.message))
} }
return res.data return res
}, },
(error: AxiosError) => { (error: AxiosError) => {
const status = error.response?.status const status = error.response?.status
+3 -3
View File
@@ -236,7 +236,7 @@ const ruleRules = {
async function fetchLevels() { async function fetchLevels() {
try { try {
const res: any = await getDataLevels() const res: any = await getDataLevels()
levels.value = res || [] levels.value = res.data || []
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message || '加载分级失败') ElMessage.error(e?.message || '加载分级失败')
} }
@@ -245,7 +245,7 @@ async function fetchLevels() {
async function fetchCategories() { async function fetchCategories() {
try { try {
const res: any = await getCategoryTree() const res: any = await getCategoryTree()
categoryTree.value = res || [] categoryTree.value = res.data || []
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message || '加载分类失败') ElMessage.error(e?.message || '加载分类失败')
} }
@@ -267,7 +267,7 @@ async function fetchRules() {
async function fetchTemplates() { async function fetchTemplates() {
try { try {
const res: any = await getTemplates() const res: any = await getTemplates()
templates.value = res || [] templates.value = res.data || []
} catch (e: any) { } catch (e: any) {
// ignore // ignore
} }
@@ -75,8 +75,7 @@ import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue' import { Search } from '@element-plus/icons-vue'
import { getProjects } from '@/api/project' import { getProjects } from '@/api/project'
import { getDataLevels } from '@/api/classification' import { getDataLevels, getClassificationResults } from '@/api/classification'
import { getTaskItems } from '@/api/task'
const loading = ref(false) const loading = ref(false)
const resultList = ref<any[]>([]) const resultList = ref<any[]>([])
@@ -100,16 +99,15 @@ function confidenceClass(v: number) {
async function fetchData() { async function fetchData() {
loading.value = true loading.value = true
try { try {
if (!filterProjectId.value) { const res: any = await getClassificationResults({
resultList.value = [] project_id: filterProjectId.value || undefined,
total.value = 0 level_id: filterLevelId.value || undefined,
return keyword: filterKeyword.value || undefined,
} page: page.value,
const res: any = await getTaskItems(-1) // use a special endpoint or fetch from project page_size: pageSize.value,
// Actually we need a dedicated results API. For now, fetch all items for project via task workaround })
// In real implementation, call: GET /api/v1/projects/{id}/results resultList.value = res.data || []
resultList.value = [] total.value = res.total || 0
total.value = 0
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message || '加载失败') ElMessage.error(e?.message || '加载失败')
} finally { } finally {
+1 -1
View File
@@ -118,7 +118,7 @@ function filterNode(value: string, data: TreeNode) {
async function fetchTree() { async function fetchTree() {
try { try {
const res: any = await getMetadataTree() const res: any = await getMetadataTree()
treeData.value = res || [] treeData.value = res.data || []
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message || '加载失败') ElMessage.error(e?.message || '加载失败')
} }
+2 -2
View File
@@ -150,7 +150,7 @@ const filteredLabelItems = computed(() => {
async function fetchData() { async function fetchData() {
try { try {
const res: any = await getMyTasks() const res: any = await getMyTasks()
tasks.value = res || [] tasks.value = res.data || []
} catch (e: any) { } catch (e: any) {
ElMessage.error(e?.message || '加载失败') ElMessage.error(e?.message || '加载失败')
} }
@@ -186,7 +186,7 @@ async function openLabel(task: TaskItem) {
} }
try { try {
const res: any = await getTaskItems(task.id) const res: any = await getTaskItems(task.id)
labelItems.value = (res || []).map((item: any) => ({ labelItems.value = (res.data || []).map((item: any) => ({
...item, ...item,
_category_id: item.category_id, _category_id: item.category_id,
_level_id: item.level_id, _level_id: item.level_id,