Files
ragflow/internal/dao/pipeline_operation_log.go
sxxtony 12579dbc3d Go: implement dataset ingestion log APIs (#15421)
### What problem does this PR solve?

Part of the Python → Go API server rewrite tracked in #15240 (Dataset
ingestion section). This PR implements the three dataset ingestion
endpoints in the Go API server, mirroring the existing Python
`dataset_api_service` behaviour:

- `GET /api/v1/datasets/<dataset_id>/ingestions/summary`
- `GET /api/v1/datasets/<dataset_id>/ingestions`
- `GET /api/v1/datasets/<dataset_id>/ingestions/<log_id>`

### Type of change

- [x] Refactoring
- [x] New Feature (non-breaking change which adds functionality)

Co-authored-by: sxxtony <sxxtony@users.noreply.github.com>
2026-06-01 11:23:44 +08:00

157 lines
5.0 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package dao
import (
"strings"
"ragflow/internal/entity"
)
// graphRaptorFakeDocID is the placeholder document_id used for dataset-level
// (graph/raptor/mindmap) pipeline logs, mirroring GRAPH_RAPTOR_FAKE_DOC_ID in
// api/db/services/task_service.py.
const graphRaptorFakeDocID = "graph_raptor_x"
// pipelineLogOrderableColumns whitelists the columns that may appear in an
// ORDER BY clause so an attacker cannot inject arbitrary SQL through the
// `orderby` query parameter.
var pipelineLogOrderableColumns = map[string]struct{}{
"id": {},
"document_id": {},
"tenant_id": {},
"kb_id": {},
"pipeline_id": {},
"pipeline_title": {},
"parser_id": {},
"document_name": {},
"document_suffix": {},
"document_type": {},
"source_from": {},
"progress": {},
"process_begin_at": {},
"process_duration": {},
"task_type": {},
"operation_status": {},
"status": {},
"create_time": {},
"create_date": {},
"update_time": {},
"update_date": {},
}
func pipelineLogOrderClause(orderby string, desc bool) string {
if _, ok := pipelineLogOrderableColumns[orderby]; !ok {
orderby = "create_time"
}
if desc {
return orderby + " DESC"
}
return orderby + " ASC"
}
// PipelineOperationLogDAO data access object for pipeline_operation_log.
type PipelineOperationLogDAO struct{}
// NewPipelineOperationLogDAO create pipeline operation log DAO.
func NewPipelineOperationLogDAO() *PipelineOperationLogDAO {
return &PipelineOperationLogDAO{}
}
// GetDatasetLogsByKBID lists dataset-level (graph/raptor/mindmap) ingestion
// logs for a knowledge base. Pagination is only applied when both page and
// pageSize are positive, matching peewee's paginate behaviour.
func (dao *PipelineOperationLogDAO) GetDatasetLogsByKBID(kbID string, page, pageSize int, orderby string, desc bool, operationStatus []string, createDateFrom, createDateTo, keywords string) ([]*entity.PipelineOperationLog, int64, error) {
query := DB.Model(&entity.PipelineOperationLog{}).
Where("kb_id = ? AND document_id = ?", kbID, graphRaptorFakeDocID)
if keywords != "" {
query = query.Where("LOWER(document_name) LIKE ?", "%"+strings.ToLower(keywords)+"%")
}
if len(operationStatus) > 0 {
query = query.Where("operation_status IN ?", operationStatus)
}
if createDateFrom != "" {
query = query.Where("create_date >= ?", createDateFrom)
}
if createDateTo != "" {
query = query.Where("create_date <= ?", createDateTo)
}
var count int64
if err := query.Count(&count).Error; err != nil {
return nil, 0, err
}
query = query.Order(pipelineLogOrderClause(orderby, desc))
if page > 0 && pageSize > 0 {
query = query.Offset((page - 1) * pageSize).Limit(pageSize)
}
var logs []*entity.PipelineOperationLog
if err := query.Find(&logs).Error; err != nil {
return nil, 0, err
}
return logs, count, nil
}
// GetFileLogsByKBID lists per-file ingestion logs for a knowledge base.
func (dao *PipelineOperationLogDAO) GetFileLogsByKBID(kbID string, page, pageSize int, orderby string, desc bool, keywords string, operationStatus []string, createDateFrom, createDateTo string) ([]*entity.PipelineOperationLog, int64, error) {
query := DB.Model(&entity.PipelineOperationLog{}).
Where("kb_id = ?", kbID)
if keywords != "" {
query = query.Where("LOWER(document_name) LIKE ?", "%"+strings.ToLower(keywords)+"%")
}
query = query.Where("document_id <> ?", graphRaptorFakeDocID)
if len(operationStatus) > 0 {
query = query.Where("operation_status IN ?", operationStatus)
}
if createDateFrom != "" {
query = query.Where("create_date >= ?", createDateFrom)
}
if createDateTo != "" {
query = query.Where("create_date <= ?", createDateTo)
}
var count int64
if err := query.Count(&count).Error; err != nil {
return nil, 0, err
}
query = query.Order(pipelineLogOrderClause(orderby, desc))
if page > 0 && pageSize > 0 {
query = query.Offset((page - 1) * pageSize).Limit(pageSize)
}
var logs []*entity.PipelineOperationLog
if err := query.Find(&logs).Error; err != nil {
return nil, 0, err
}
return logs, count, nil
}
// GetByIDAndKBID fetches a single ingestion log scoped to its knowledge base.
func (dao *PipelineOperationLogDAO) GetByIDAndKBID(logID, kbID string) (*entity.PipelineOperationLog, error) {
var log entity.PipelineOperationLog
if err := DB.Where("id = ? AND kb_id = ?", logID, kbID).First(&log).Error; err != nil {
return nil, err
}
return &log, nil
}