Skip to main content

teamcraft/domain/
files_hint.rs

1//! What the *Files* card of a revision tells the user about adding files:
2//! pure decision, shared by the page and its tests.
3
4use uuid::Uuid;
5
6use super::LifecycleState;
7
8/// The way to add files that the *Files* card shows.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum FilesHint {
11    /// Checked out by the user: the upload form.
12    Upload,
13    /// Free and the user may work on it: *Check out and add files*.
14    CheckOut,
15    /// Checked out by somebody else: files can be added after check-in.
16    LockedByOther,
17    /// In review: frozen until the change is decided.
18    InReview,
19    /// Released or obsolete: frozen, a new revision takes the files.
20    Frozen { can_revise: bool },
21    /// Nothing to say (viewers, users outside the project group).
22    Hidden,
23}
24
25impl FilesHint {
26    /// Template key.
27    pub fn as_str(self) -> &'static str {
28        match self {
29            FilesHint::Upload => "upload",
30            FilesHint::CheckOut => "checkout",
31            FilesHint::LockedByOther => "locked",
32            FilesHint::InReview => "review",
33            FilesHint::Frozen { .. } => "frozen",
34            FilesHint::Hidden => "hidden",
35        }
36    }
37}
38
39/// `editable`: the user holds the check-out and may modify (the rule of
40/// `ensure_editable`); `can_work`: the user may check out (project group,
41/// role, revision in work, not locked by someone else).
42pub fn files_hint(
43    state: LifecycleState,
44    locked_by: Option<Uuid>,
45    me: Uuid,
46    editable: bool,
47    can_work: bool,
48    can_revise: bool,
49) -> FilesHint {
50    if editable {
51        return FilesHint::Upload;
52    }
53    match state {
54        LifecycleState::InWork => match locked_by {
55            Some(owner) if owner != me => FilesHint::LockedByOther,
56            Some(_) => FilesHint::Hidden,
57            None if can_work => FilesHint::CheckOut,
58            None => FilesHint::Hidden,
59        },
60        LifecycleState::InReview => FilesHint::InReview,
61        LifecycleState::Released | LifecycleState::Obsolete => FilesHint::Frozen { can_revise },
62    }
63}
64
65/// What to put on an item of this type (the `item_type` text value).
66pub fn files_type_hint(item_type: &str) -> Option<&'static str> {
67    match item_type {
68        "simulation" => Some(
69            "Solver input, models, result files and reports. Large result files are stored, not parsed.",
70        ),
71        "part" => Some("The CAD model, the drawing and a STEP export (for the 3D preview)."),
72        _ => None,
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use LifecycleState::*;
80
81    #[test]
82    fn each_state_has_its_hint() {
83        let me = Uuid::new_v4();
84        let other = Uuid::new_v4();
85        let h = |s, l, e, w, r| files_hint(s, l, me, e, w, r);
86        assert_eq!(h(InWork, Some(me), true, true, false), FilesHint::Upload);
87        assert_eq!(h(InWork, None, false, true, false), FilesHint::CheckOut);
88        assert_eq!(
89            h(InWork, Some(other), false, false, false),
90            FilesHint::LockedByOther
91        );
92        assert_eq!(h(InReview, None, false, false, false), FilesHint::InReview);
93        assert_eq!(
94            h(Released, None, false, false, true),
95            FilesHint::Frozen { can_revise: true }
96        );
97        assert_eq!(
98            h(Obsolete, None, false, false, false),
99            FilesHint::Frozen { can_revise: false }
100        );
101    }
102
103    #[test]
104    fn viewers_and_outsiders_get_no_add_path() {
105        let me = Uuid::new_v4();
106        assert_eq!(
107            files_hint(InWork, None, me, false, false, false),
108            FilesHint::Hidden
109        );
110        assert_eq!(
111            files_hint(InWork, Some(me), me, false, false, false),
112            FilesHint::Hidden
113        );
114    }
115
116    #[test]
117    fn type_hints() {
118        assert!(files_type_hint("simulation").unwrap().contains("result"));
119        assert!(files_type_hint("part").unwrap().contains("STEP"));
120        assert!(files_type_hint("document").is_none());
121    }
122
123    /// The names `templates/revisions/show.html` switches on (`fh.kind`).
124    #[test]
125    fn kinds_are_the_names_the_template_uses() {
126        let kinds = [
127            (FilesHint::Upload, "upload"),
128            (FilesHint::CheckOut, "checkout"),
129            (FilesHint::LockedByOther, "locked"),
130            (FilesHint::InReview, "review"),
131            (FilesHint::Frozen { can_revise: true }, "frozen"),
132            (FilesHint::Frozen { can_revise: false }, "frozen"),
133            (FilesHint::Hidden, "hidden"),
134        ];
135        let template = include_str!("../../templates/revisions/show.html");
136        for (hint, name) in kinds {
137            assert_eq!(hint.as_str(), name);
138            if hint != FilesHint::Hidden {
139                assert!(
140                    template.contains(&format!("fh.kind == \"{name}\"")),
141                    "the template handles {name}"
142                );
143            }
144        }
145    }
146}