feat: 收口 tree-first graph 主链与前端测试修复
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
use crate::app::AppConfig;
|
||||
use crate::context::RequestContext;
|
||||
use crate::error::WebError;
|
||||
use crate::routes::query_support::{
|
||||
execute_runtime_query_via_convex, resolve_effective_workspace_id,
|
||||
};
|
||||
use crate::routes::snapshot_support::{
|
||||
execute_kernel_query, load_projection_snapshot, load_sidebar_dataset, subtree_query,
|
||||
ProjectionSnapshotSpec,
|
||||
};
|
||||
use bridge_runtime::RuntimeQueryEnvelopeWire;
|
||||
use core_protocol::KernelProjectionKind;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct StreamSnapshotQuery {
|
||||
pub workspace_id: Option<String>,
|
||||
pub root_node_id: Option<String>,
|
||||
pub depth: Option<u32>,
|
||||
pub cursor: Option<String>,
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StreamSnapshotScope {
|
||||
Workspace,
|
||||
Subtree,
|
||||
}
|
||||
|
||||
impl StreamSnapshotScope {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Workspace => "workspace",
|
||||
Self::Subtree => "subtree",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_stream_scope(query: &StreamSnapshotQuery) -> StreamSnapshotScope {
|
||||
if query
|
||||
.root_node_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_some()
|
||||
{
|
||||
StreamSnapshotScope::Subtree
|
||||
} else {
|
||||
StreamSnapshotScope::Workspace
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_overview_query(
|
||||
workspace_id: &str,
|
||||
query: &StreamSnapshotQuery,
|
||||
) -> RuntimeQueryEnvelopeWire {
|
||||
RuntimeQueryEnvelopeWire {
|
||||
name: "bridge.workspace.overview".into(),
|
||||
payload: json!({
|
||||
"workspaceId": workspace_id,
|
||||
"cursor": query.cursor,
|
||||
"limit": query.limit.unwrap_or(20),
|
||||
"commandStatus": Value::Null,
|
||||
"eventStatus": Value::Null,
|
||||
"targetPageId": Value::Null,
|
||||
"targetBlockId": Value::Null,
|
||||
"aggregateType": Value::Null,
|
||||
"aggregateId": query.root_node_id,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_stream_snapshot(
|
||||
config: &AppConfig,
|
||||
context: &RequestContext,
|
||||
query: &StreamSnapshotQuery,
|
||||
) -> Result<Value, WebError> {
|
||||
let effective_workspace_id =
|
||||
resolve_effective_workspace_id(context, query.workspace_id.as_deref(), true)?
|
||||
.expect("workspace_required 已确保存在");
|
||||
let scope = resolve_stream_scope(query);
|
||||
|
||||
let snapshot = match scope {
|
||||
StreamSnapshotScope::Workspace => {
|
||||
let loaded = load_projection_snapshot(
|
||||
config,
|
||||
context,
|
||||
&ProjectionSnapshotSpec {
|
||||
workspace_id: &effective_workspace_id,
|
||||
root_node_id: None,
|
||||
depth: query.depth,
|
||||
projection: KernelProjectionKind::SidebarTree,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
json!({
|
||||
"dataset": loaded.dataset,
|
||||
"tree": loaded.projection,
|
||||
})
|
||||
}
|
||||
StreamSnapshotScope::Subtree => {
|
||||
let root_node_id = query
|
||||
.root_node_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.expect("subtree scope 已确保 rootNodeId 存在");
|
||||
let dataset = load_sidebar_dataset(config, context, &effective_workspace_id).await?;
|
||||
let tree = execute_kernel_query(
|
||||
context,
|
||||
&effective_workspace_id,
|
||||
subtree_query(&effective_workspace_id, root_node_id, query.depth),
|
||||
dataset.clone(),
|
||||
)?;
|
||||
|
||||
json!({
|
||||
"dataset": dataset,
|
||||
"tree": tree,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let overview = execute_runtime_query_via_convex(
|
||||
config,
|
||||
context,
|
||||
Some(&effective_workspace_id),
|
||||
workspace_overview_query(&effective_workspace_id, query),
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
Ok(json!({
|
||||
"kind": "snapshot",
|
||||
"scope": scope.as_str(),
|
||||
"requestId": context.trace.request_id,
|
||||
"traceId": context.trace.trace_id,
|
||||
"workspaceId": effective_workspace_id,
|
||||
"rootNodeId": query.root_node_id,
|
||||
"depth": query.depth,
|
||||
"snapshot": snapshot,
|
||||
"overview": overview,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{resolve_stream_scope, StreamSnapshotQuery, StreamSnapshotScope};
|
||||
|
||||
#[test]
|
||||
fn stream_scope_defaults_to_workspace() {
|
||||
assert_eq!(
|
||||
resolve_stream_scope(&StreamSnapshotQuery::default()),
|
||||
StreamSnapshotScope::Workspace
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_scope_switches_to_subtree_when_root_exists() {
|
||||
assert_eq!(
|
||||
resolve_stream_scope(&StreamSnapshotQuery {
|
||||
root_node_id: Some("page_root".into()),
|
||||
..StreamSnapshotQuery::default()
|
||||
}),
|
||||
StreamSnapshotScope::Subtree
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user