Align approvals and documents with native shell
This commit is contained in:
1 parent
6809df15de
commit
b3d5bcdde5
3 files changed
+419
-134
No files matched your search
@@ -2,100 +2,238 @@ use super::*;
|
|||||||
|
|
||||||
impl AppRoot {
|
impl AppRoot {
|
||||||
pub(super) fn render_approvals(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
pub(super) fn render_approvals(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
|
||||||
let selected = self
|
let selected_pending_id = self
|
||||||
.state
|
.state
|
||||||
.selected_approval_id
|
.selected_approval_id
|
||||||
.as_ref()
|
.clone()
|
||||||
.and_then(|id| self.snapshot.approvals.iter().find(|item| &item.id == id));
|
.filter(|approval_id| {
|
||||||
let approval_type = selected
|
pending_approvals(&self.snapshot).any(|item| &item.id == approval_id)
|
||||||
.map(|item| item.approval_type.clone())
|
});
|
||||||
.unwrap_or_else(|| "Select an approval".to_string());
|
let selected_pending = selected_pending_id.as_ref().and_then(|approval_id| {
|
||||||
let approval_summary = selected
|
self.snapshot
|
||||||
.map(|item| item.impact_summary.clone())
|
.approvals
|
||||||
.unwrap_or_else(|| "No approval selected.".to_string());
|
.iter()
|
||||||
|
.find(|item| &item.id == approval_id)
|
||||||
|
});
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_4()
|
.flex_col()
|
||||||
|
.gap_10()
|
||||||
.child(
|
.child(
|
||||||
div()
|
approvals_section(
|
||||||
.id("approvals-list")
|
"Agent reviews in progress",
|
||||||
.w(px(340.0))
|
self.snapshot.tasks.iter().filter(|task| task.status == "in_review"),
|
||||||
.p_4()
|
),
|
||||||
.gap_2()
|
|
||||||
.border_1()
|
|
||||||
.rounded_md()
|
|
||||||
.border_color(theme::panel_border())
|
|
||||||
.bg(theme::panel())
|
|
||||||
.overflow_scroll()
|
|
||||||
.child(div().text_lg().child("Approval queue"))
|
|
||||||
.children(self.snapshot.approvals.iter().map(|item| {
|
|
||||||
let active =
|
|
||||||
self.state.selected_approval_id.as_deref() == Some(item.id.as_str());
|
|
||||||
let approval_id = item.id.clone();
|
|
||||||
div()
|
|
||||||
.id(SharedString::from(format!("approval-{}", item.id)))
|
|
||||||
.px_3()
|
|
||||||
.py_2()
|
|
||||||
.rounded_md()
|
|
||||||
.cursor_pointer()
|
|
||||||
.bg(if active {
|
|
||||||
theme::selection_fill()
|
|
||||||
} else {
|
|
||||||
theme::panel()
|
|
||||||
})
|
|
||||||
.hover(|style| style.bg(theme::panel_hover()))
|
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
|
||||||
this.state.selected_approval_id = Some(approval_id.clone());
|
|
||||||
cx.notify();
|
|
||||||
}))
|
|
||||||
.child(format!("{} · {}", item.state, item.payload_summary))
|
|
||||||
})),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.gap_5()
|
||||||
.p_4()
|
|
||||||
.gap_3()
|
|
||||||
.border_1()
|
|
||||||
.rounded_md()
|
|
||||||
.border_color(theme::panel_border())
|
|
||||||
.bg(theme::panel())
|
|
||||||
.child(div().text_lg().child("Decision"))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_sm()
|
.text_2xl()
|
||||||
.text_color(theme::text_muted())
|
.text_color(theme::text_primary())
|
||||||
.child(approval_type),
|
.child("Pending approvals"),
|
||||||
)
|
)
|
||||||
.child(
|
.child(div().h(px(1.0)).bg(theme::panel_border()))
|
||||||
div()
|
.when(self.snapshot.pending_approvals() == 0, |container| {
|
||||||
.min_h(px(160.0))
|
container.child(
|
||||||
.p_3()
|
div()
|
||||||
.border_1()
|
.min_h(px(280.0))
|
||||||
.border_color(theme::panel_inset_border())
|
.flex()
|
||||||
.rounded_md()
|
.items_center()
|
||||||
.bg(theme::panel_inset())
|
.justify_center()
|
||||||
.child(approval_summary),
|
.child(
|
||||||
)
|
div()
|
||||||
.child(
|
.w(px(520.0))
|
||||||
div()
|
.text_center()
|
||||||
.flex()
|
.child(
|
||||||
.gap_2()
|
div()
|
||||||
.child(primary_button("Approve", cx, |this, cx| {
|
.text_xl()
|
||||||
this.decide_selected_approval(
|
.text_color(theme::text_primary())
|
||||||
ApprovalState::Approved,
|
.child("No pending approvals"),
|
||||||
"Approved from native shell.",
|
)
|
||||||
cx,
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(10.0))
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(
|
||||||
|
"Approval requests from agents will appear here when they need your sign-off.",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(self.snapshot.pending_approvals() > 0, |container| {
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.id("approvals-pending-list")
|
||||||
|
.border_1()
|
||||||
|
.rounded_lg()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
|
.bg(theme::panel())
|
||||||
|
.children(pending_approvals(&self.snapshot).map(|item| {
|
||||||
|
let active =
|
||||||
|
selected_pending_id.as_deref() == Some(item.id.as_str());
|
||||||
|
let approval_id = item.id.clone();
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("approval-{}", item.id)))
|
||||||
|
.px_5()
|
||||||
|
.py_4()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
|
.cursor_pointer()
|
||||||
|
.bg(if active {
|
||||||
|
theme::selection_fill()
|
||||||
|
} else {
|
||||||
|
theme::panel()
|
||||||
|
})
|
||||||
|
.hover(|style| style.bg(theme::panel_hover()))
|
||||||
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
|
this.state.selected_approval_id = Some(approval_id.clone());
|
||||||
|
cx.notify();
|
||||||
|
}))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_primary())
|
||||||
|
.child(item.payload_summary.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(6.0))
|
||||||
|
.text_sm()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(format!(
|
||||||
|
"{} · {}",
|
||||||
|
item.approval_type.replace('_', " "),
|
||||||
|
item.state
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when_some(selected_pending, |container, approval| {
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.mt(px(8.0))
|
||||||
|
.p_5()
|
||||||
|
.rounded_lg()
|
||||||
|
.border_1()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
|
.bg(theme::panel())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(approval.approval_type.replace('_', " ")),
|
||||||
)
|
)
|
||||||
}))
|
.child(
|
||||||
.child(secondary_button("Reject", cx, |this, cx| {
|
div()
|
||||||
this.decide_selected_approval(
|
.mt(px(10.0))
|
||||||
ApprovalState::Rejected,
|
.text_lg()
|
||||||
"Rejected from native shell.",
|
.text_color(theme::text_primary())
|
||||||
cx,
|
.child(approval.payload_summary.clone()),
|
||||||
)
|
)
|
||||||
})),
|
.child(
|
||||||
),
|
div()
|
||||||
|
.mt(px(10.0))
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_secondary())
|
||||||
|
.child(approval.impact_summary.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(18.0))
|
||||||
|
.flex()
|
||||||
|
.gap_3()
|
||||||
|
.child(primary_button("Approve", cx, |this, cx| {
|
||||||
|
this.decide_selected_approval(
|
||||||
|
ApprovalState::Approved,
|
||||||
|
"Approved from native shell.",
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.child(secondary_button("Reject", cx, |this, cx| {
|
||||||
|
this.decide_selected_approval(
|
||||||
|
ApprovalState::Rejected,
|
||||||
|
"Rejected from native shell.",
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn approvals_section<'a>(
|
||||||
|
title: &'static str,
|
||||||
|
tasks: impl Iterator<Item = &'a domain::Task>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let rows = tasks.collect::<Vec<_>>();
|
||||||
|
div()
|
||||||
|
.gap_5()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_2xl()
|
||||||
|
.text_color(theme::text_primary())
|
||||||
|
.child(title),
|
||||||
|
)
|
||||||
|
.child(div().h(px(1.0)).bg(theme::panel_border()))
|
||||||
|
.when(rows.is_empty(), |container| {
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.py_8()
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child("No agent reviews are currently waiting on a human."),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!rows.is_empty(), |container| {
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.border_1()
|
||||||
|
.rounded_lg()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
|
.bg(theme::panel())
|
||||||
|
.children(rows.into_iter().map(|task| {
|
||||||
|
div()
|
||||||
|
.px_5()
|
||||||
|
.py_4()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_primary())
|
||||||
|
.truncate()
|
||||||
|
.child(task.title.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(6.0))
|
||||||
|
.text_sm()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(format!(
|
||||||
|
"{} · awaiting review",
|
||||||
|
task.assignee_agent_id
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "CEO".to_string())
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_approvals(
|
||||||
|
snapshot: &domain::ProfileSnapshot,
|
||||||
|
) -> impl Iterator<Item = &domain::Approval> {
|
||||||
|
snapshot
|
||||||
|
.approvals
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.state == "pending" || item.state == "revision_requested")
|
||||||
|
}
|
||||||
@@ -7,82 +7,147 @@ impl AppRoot {
|
|||||||
.selected_document_id
|
.selected_document_id
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|id| self.snapshot.documents.iter().find(|item| &item.id == id));
|
.and_then(|id| self.snapshot.documents.iter().find(|item| &item.id == id));
|
||||||
let document_title = selected
|
|
||||||
.map(|item| item.title.clone())
|
|
||||||
.unwrap_or_else(|| "Select a document".to_string());
|
|
||||||
let document_content = selected
|
|
||||||
.map(|item| item.content.clone())
|
|
||||||
.unwrap_or_else(|| "No document selected.".to_string());
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.gap_4()
|
.flex_col()
|
||||||
|
.gap_6()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("documents-list")
|
.flex()
|
||||||
.w(px(340.0))
|
.justify_between()
|
||||||
.p_4()
|
.items_start()
|
||||||
.gap_2()
|
.child(
|
||||||
.border_1()
|
div()
|
||||||
.rounded_md()
|
.child(
|
||||||
.border_color(theme::panel_border())
|
div()
|
||||||
.bg(theme::panel())
|
.text_2xl()
|
||||||
.overflow_scroll()
|
.text_color(theme::text_primary())
|
||||||
.child(div().text_lg().child("Document center"))
|
.child("Document Center"),
|
||||||
.children(self.snapshot.documents.iter().map(|item| {
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(6.0))
|
||||||
|
.text_sm()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(format!("{} items", self.snapshot.documents.len())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(primary_button("Save draft", cx, |this, cx| {
|
||||||
|
this.create_document(cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(div().h(px(1.0)).bg(theme::panel_border()))
|
||||||
|
.when(self.snapshot.documents.is_empty(), |container| {
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.py_8()
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child("No documents have been created yet."),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!self.snapshot.documents.is_empty(), |container| {
|
||||||
|
container.child(div().id("documents-list").children(
|
||||||
|
self.snapshot.documents.iter().map(|item| {
|
||||||
let active =
|
let active =
|
||||||
self.state.selected_document_id.as_deref() == Some(item.id.as_str());
|
self.state.selected_document_id.as_deref() == Some(item.id.as_str());
|
||||||
let document_id = item.id.clone();
|
let document_id = item.id.clone();
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from(format!("document-{}", item.id)))
|
.id(SharedString::from(format!("document-{}", item.id)))
|
||||||
.px_3()
|
.flex()
|
||||||
.py_2()
|
.justify_between()
|
||||||
.rounded_md()
|
.items_center()
|
||||||
|
.px_6()
|
||||||
|
.py_5()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(theme::panel_border())
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.bg(if active {
|
.bg(if active {
|
||||||
theme::selection_fill()
|
theme::selection_fill()
|
||||||
} else {
|
} else {
|
||||||
theme::panel()
|
theme::window_bg()
|
||||||
})
|
})
|
||||||
.hover(|style| style.bg(theme::panel_hover()))
|
.hover(|style| style.bg(theme::panel_hover()))
|
||||||
.on_click(cx.listener(move |this, _, _, cx| {
|
.on_click(cx.listener(move |this, _, _, cx| {
|
||||||
this.state.selected_document_id = Some(document_id.clone());
|
this.state.selected_document_id = Some(document_id.clone());
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
.child(format!("{} · {}", item.status, item.title))
|
.child(
|
||||||
})),
|
div()
|
||||||
)
|
.flex_1()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_base()
|
||||||
|
.text_color(theme::text_primary())
|
||||||
|
.truncate()
|
||||||
|
.child(item.title.clone()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.mt(px(8.0))
|
||||||
|
.text_sm()
|
||||||
|
.text_color(theme::text_muted())
|
||||||
|
.child(format!(
|
||||||
|
"{} · v{} · {}",
|
||||||
|
item.document_type.replace('_', " "),
|
||||||
|
item.version,
|
||||||
|
short_date(item.updated_at.as_str())
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(document_status(item.status.as_str()))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.child(div().h(px(1.0)).bg(theme::panel_border()))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.gap_4()
|
||||||
.p_4()
|
.child(
|
||||||
.gap_3()
|
div().text_lg().text_color(theme::text_primary()).child(
|
||||||
.border_1()
|
selected
|
||||||
.rounded_md()
|
.map(|item| item.title.clone())
|
||||||
.border_color(theme::panel_border())
|
.unwrap_or_else(|| "Quick draft".to_string()),
|
||||||
.bg(theme::panel())
|
),
|
||||||
.child(div().text_lg().child("Quick draft"))
|
)
|
||||||
.child(self.document_title_input.clone())
|
.child(self.document_title_input.clone())
|
||||||
.child(self.document_body_input.clone())
|
.child(self.document_body_input.clone())
|
||||||
.child(primary_button("Save draft", cx, |this, cx| {
|
.when_some(selected, |container, item| {
|
||||||
this.create_document(cx)
|
container.child(
|
||||||
}))
|
div()
|
||||||
.child(div().h(px(1.0)).bg(theme::panel_inset_border()))
|
.p_4()
|
||||||
.child(
|
.rounded_lg()
|
||||||
div()
|
.border_1()
|
||||||
.text_sm()
|
.border_color(theme::panel_inset_border())
|
||||||
.text_color(theme::text_muted())
|
.bg(theme::panel_inset())
|
||||||
.child(document_title),
|
.child(item.content.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
}),
|
||||||
div()
|
|
||||||
.min_h(px(200.0))
|
|
||||||
.p_3()
|
|
||||||
.border_1()
|
|
||||||
.border_color(theme::panel_inset_border())
|
|
||||||
.rounded_md()
|
|
||||||
.bg(theme::panel_inset())
|
|
||||||
.child(document_content),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn document_status(status: &str) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(px(8.0))
|
||||||
|
.h(px(8.0))
|
||||||
|
.rounded_full()
|
||||||
|
.bg(theme::badge_dark()),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(theme::text_secondary())
|
||||||
|
.child(status.to_uppercase()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_date(value: &str) -> &str {
|
||||||
|
value.get(..10).unwrap_or(value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
use storage::{NewCompanyInput, ProfileStore};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn native_surface_snapshot_round_trips_reviews_runs_and_documents() {
|
||||||
|
let sandbox = tempdir().expect("temp dir");
|
||||||
|
let mut store = ProfileStore::open(sandbox.path()).expect("open store");
|
||||||
|
|
||||||
|
let company_id = store
|
||||||
|
.create_company(NewCompanyInput {
|
||||||
|
name: "Orbit Ops".to_string(),
|
||||||
|
description: "Local service operator".to_string(),
|
||||||
|
workspace_path: "/tmp/orbit-ops".to_string(),
|
||||||
|
execution_mode: "ai_ceo".to_string(),
|
||||||
|
})
|
||||||
|
.expect("create company");
|
||||||
|
|
||||||
|
let ts = support::time::now_iso().expect("timestamp");
|
||||||
|
let task_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let approval_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let run_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
let conn = rusqlite::Connection::open(store.db_path()).expect("open raw db");
|
||||||
|
conn.execute(
|
||||||
|
"insert into tasks (id, company_id, project_id, goal_id, parent_id, title, description, assignee_agent_id, workspace_id, status, priority, active_run_id, cost_usd, task_type, requires_user_review, started_at, completed_at, cancelled_at, created_at, updated_at) values (?1, ?2, null, null, null, 'Operationalize launch plan', 'Awaiting review from board', null, null, 'in_review', 'high', ?3, 0, 'review', 1, ?4, null, null, ?4, ?4)",
|
||||||
|
rusqlite::params![task_id, company_id, run_id, ts],
|
||||||
|
)
|
||||||
|
.expect("insert review task");
|
||||||
|
conn.execute(
|
||||||
|
"insert into approvals (id, company_id, related_task_id, requested_by_agent_id, related_agent_id, type, payload_summary, impact_summary, payload_json, decision_note, state, created_at, updated_at) values (?1, ?2, ?3, null, null, 'plan_review', 'Review launch plan', 'Board decision blocks execution', null, '', 'pending', ?4, ?4)",
|
||||||
|
rusqlite::params![approval_id, company_id, task_id, ts],
|
||||||
|
)
|
||||||
|
.expect("insert pending approval");
|
||||||
|
conn.execute(
|
||||||
|
"insert into runs (id, company_id, task_id, agent_id, workspace_id, connector_id, status, summary, error_message, model, cost_usd, started_at, finished_at, created_at, updated_at) values (?1, ?2, ?3, '__board__', null, 'anthropic', 'running', 'Executing launch checklist', null, 'claude-sonnet', 1.5, ?4, null, ?4, ?4)",
|
||||||
|
rusqlite::params![run_id, company_id, task_id, ts],
|
||||||
|
)
|
||||||
|
.expect("insert running run");
|
||||||
|
|
||||||
|
let document_id = store
|
||||||
|
.create_document(
|
||||||
|
&company_id,
|
||||||
|
"Launch brief",
|
||||||
|
"Board-ready operating brief for the native shell.",
|
||||||
|
)
|
||||||
|
.expect("create document");
|
||||||
|
|
||||||
|
let snapshot = store.snapshot().expect("snapshot");
|
||||||
|
assert_eq!(
|
||||||
|
snapshot.current_company_id.as_deref(),
|
||||||
|
Some(company_id.as_str())
|
||||||
|
);
|
||||||
|
assert_eq!(snapshot.pending_approvals(), 1);
|
||||||
|
assert_eq!(snapshot.active_runs(), 1);
|
||||||
|
assert_eq!(snapshot.documents.len(), 1);
|
||||||
|
assert_eq!(snapshot.documents[0].id, document_id);
|
||||||
|
assert!(snapshot.tasks.iter().any(|task| task.id == task_id));
|
||||||
|
assert!(
|
||||||
|
snapshot
|
||||||
|
.approvals
|
||||||
|
.iter()
|
||||||
|
.any(|approval| approval.id == approval_id)
|
||||||
|
);
|
||||||
|
assert!(snapshot.runs.iter().any(|run| run.id == run_id));
|
||||||
|
|
||||||
|
let reopened = ProfileStore::open(sandbox.path()).expect("reopen store");
|
||||||
|
let reopened_snapshot = reopened.snapshot().expect("reopened snapshot");
|
||||||
|
assert_eq!(reopened_snapshot.pending_approvals(), 1);
|
||||||
|
assert_eq!(reopened_snapshot.active_runs(), 1);
|
||||||
|
assert!(
|
||||||
|
reopened_snapshot
|
||||||
|
.tasks
|
||||||
|
.iter()
|
||||||
|
.any(|task| task.id == task_id)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
reopened_snapshot
|
||||||
|
.documents
|
||||||
|
.iter()
|
||||||
|
.any(|document| document.id == document_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user