父⑥ Phase 2(对标设计 §2.4): - V30 迁移:project_events 追加型审计表(id/project_id/event_type/entity_type/entity_id/from_state/to_state/context_json/source/conversation_id/created_at)+ 双索引(project,time / entity) - ProjectEventRecord + ProjectEventRepo(insert 追加型/get_by_project/get_by_entity/list_recent) - 事件埋点(best-effort,hook/after,失败 warn 不阻断主操作): - task.rs emit_event 辅助 + create_task/advance_task/move_task_queue 埋点 - idea.rs promote_idea 埋点(idea_promoted,project_id=新项目) - project.rs create/delete 埋点 - 注:idea_created 暂不埋点(idea 无 project_id,待 Inbox 项目落地,NOT NULL+FK 约束) - get_project_timeline IPC(events 模块)+ lib.rs 注册 + AI 工具(基线 +N) - source 语义:IPC 标 human,AI 工具路径标 ai 注:workflow 脚本中文变量名 const 致验证 agent 未跑,主控独立 cargo check EXIT 0 + grep 落地齐全核验。
92 lines
3.4 KiB
Rust
92 lines
3.4 KiB
Rust
//! 项目事件流相关命令(知识图谱 Phase 2 V30,对标设计 §2.4 + §五 get_project_timeline)
|
|
//!
|
|
//! 统一事件流 project_events 的查询入口。供 AI / Dashboard 项目时间线视图使用
|
|
//! (设计 §10.3.4 模式 B 事件流 + §五 get_project_timeline)。
|
|
//!
|
|
//! 筛选策略:Repo::get_by_project 取最近 N 条(上限 200,对标设计 §10.5 单用户桌面应用
|
|
//! 全量查询无压力),应用层按 event_type / 时间范围过滤。复杂多维筛选不进 SQL(数据量小,
|
|
//! 应用层过滤足够 + 保持 Repo 专用方法精简,对标 TaskLinkRepo 全专用路径)。
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use tauri::State;
|
|
|
|
use df_storage::models::ProjectEventRecord;
|
|
|
|
use crate::state::AppState;
|
|
|
|
use super::err_str;
|
|
|
|
/// get_project_timeline 查询入参(对标设计 §五 get_project_timeline:time 范围/event_type 筛选)。
|
|
///
|
|
/// 所有字段可选:
|
|
/// - `project_id`:必填(查询某项目的事件流)。
|
|
/// - `event_type`:可选,按事件类型精确过滤(如 "task_advanced");空/None = 不过滤。
|
|
/// - `limit`:可选,返回上限(默认 50,Repo 内部钳制最大 200)。
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct GetTimelineInput {
|
|
pub project_id: String,
|
|
/// 事件类型过滤(精确匹配,如 "task_created" / "idea_promoted")。None/空串 = 不过滤。
|
|
pub event_type: Option<String>,
|
|
/// 返回条数上限(默认 50)。Repo 内部钳制 ≤ 200。
|
|
#[serde(default = "default_limit")]
|
|
pub limit: u32,
|
|
}
|
|
|
|
fn default_limit() -> u32 {
|
|
50
|
|
}
|
|
|
|
/// 时间线条目(对标设计 §10.3.4 模式 B 事件流)。直接回 ProjectEventRecord(Serialize 已派生),
|
|
/// 外层补 total 便于前端分页/计数。
|
|
#[derive(Debug, Serialize)]
|
|
pub struct TimelineResult {
|
|
/// 过滤后的事件列表(时间倒序,Repo 已排)
|
|
pub items: Vec<ProjectEventRecord>,
|
|
/// 过滤后总数(≤ limit)
|
|
pub total: usize,
|
|
/// 查询的项目 ID(回显)
|
|
pub project_id: String,
|
|
}
|
|
|
|
/// 查询项目事件流(对标设计 §五 get_project_timeline + §2.4)。
|
|
///
|
|
/// 支持按 event_type 过滤 + limit 上限。时间范围过滤暂未实现(设计 §2.4 列出 time 范围筛选,
|
|
/// 但当前 get_by_project 仅返回最近 N 条倒序,时间范围筛选用例可后续按需加 Repo 方法;
|
|
/// 单用户桌面应用事件量小,最近 N 条 + event_type 过滤已覆盖主要场景)。
|
|
#[tauri::command]
|
|
pub async fn get_project_timeline(
|
|
state: State<'_, AppState>,
|
|
input: GetTimelineInput,
|
|
) -> Result<TimelineResult, String> {
|
|
let project_id = input.project_id.trim().to_string();
|
|
if project_id.is_empty() {
|
|
return Err("project_id 不能为空".to_string());
|
|
}
|
|
// event_type 过滤值规范化(None/空串 = 不过滤)
|
|
let event_filter = input
|
|
.event_type
|
|
.as_deref()
|
|
.map(str::trim)
|
|
.filter(|s| !s.is_empty())
|
|
.map(|s| s.to_string());
|
|
|
|
// Repo 取最近 limit 条(内部钳制 ≤ 200),时间倒序。
|
|
let mut events = state
|
|
.project_events
|
|
.get_by_project(&project_id, input.limit)
|
|
.await
|
|
.map_err(err_str)?;
|
|
|
|
// 应用层按 event_type 过滤(SQL 下推不值得——单用户事件量小,保持 Repo 方法精简)。
|
|
if let Some(et) = &event_filter {
|
|
events.retain(|e| e.event_type == *et);
|
|
}
|
|
|
|
let total = events.len();
|
|
Ok(TimelineResult {
|
|
items: events,
|
|
total,
|
|
project_id,
|
|
})
|
|
}
|