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:
@@ -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,
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 || '加载失败')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user