Skip to main content

forge_server/surface/
board.rs

1//! `board()` and `fleet()`: the task board as a view reads it.
2//!
3//! Both forward the workspace's own answer - the marks are derived in ONE
4//! place (forge-workspace's board module), and this surface passes them
5//! through rather than recomputing anything, so no two surfaces can draw
6//! a row differently.
7
8use std::time::SystemTime;
9
10use forge_workspace::board::{BoardRow, FleetRow};
11
12use crate::surface::ViewSurface;
13
14impl ViewSurface {
15    /// `project`'s live rows, with their derived facts.
16    pub fn board_rows(&self, project: &str) -> Vec<BoardRow> {
17        self.workspace.board_rows(
18            project,
19            SystemTime::now(),
20            forge_workspace::board::DEFAULT_STALE_SECS,
21        )
22    }
23
24    /// One row per project, for the fleet page.
25    pub fn fleet(&self) -> Vec<FleetRow> {
26        self.workspace.fleet_rows()
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use std::sync::Arc;
33
34    use forge_primitives::SessionSlot;
35    use forge_primitives::tasks::{Task, TaskId, TaskStatus};
36    use forge_workspace::Workspace;
37
38    use crate::surface::ViewSurface;
39
40    fn sample_task(id: &str, project: &str) -> Task {
41        Task {
42            id: TaskId::from(id),
43            project_name: project.to_owned(),
44            subject: format!("subject {id}"),
45            active_form: None,
46            detail: None,
47            status: TaskStatus::Pending,
48            owner: None,
49            parent: None,
50            waiting_on: None,
51            estimate: None,
52            rank: None,
53            verify: None,
54            attested: None,
55            reserved: None,
56            links: Vec::new(),
57            attempt: 0,
58            archived_at: None,
59            created_at: std::time::SystemTime::UNIX_EPOCH,
60            updated_at: std::time::SystemTime::UNIX_EPOCH,
61        }
62    }
63
64    /// Catches a board read that recomputes marks instead of forwarding
65    /// the workspace's own answer, or that forgets the project scoping.
66    #[test]
67    fn the_board_verbs_forward_the_workspaces_own_answer() {
68        let (workspace, _updates) = Workspace::testing_stub();
69        workspace.seed_test_project("forge", "/tmp/forge-surface-board");
70        workspace.seed_test_task(sample_task("t-1", "forge"));
71        let surface = ViewSurface::new(Arc::clone(&workspace));
72
73        let rows = surface.board_rows("forge");
74        assert_eq!(rows.len(), 1, "the seeded row is on the board");
75        assert!(rows[0].marks.ready, "a pending unowned row is ready");
76        assert!(surface.board_rows("other").is_empty(), "and another project sees none");
77
78        let fleet = surface.fleet();
79        let row = fleet.iter().find(|row| row.project == "forge").expect("the project's row");
80        assert_eq!(row.queue, 1, "the ready unowned row is the queue");
81        let _ = SessionSlot::lead("TestOrg", "forge");
82    }
83}