Make work items workspace-level in the contracts
Issues become work items numbered in their workspace and linked to the repositories they concern (origin, target, touched), with a resolution per repository they land in. Labels and milestones belong to the workspace; comments, reactions and labels name their thread by repository and pull request number or by workspace and work item number. Coordination (claims, sessions, get_work) is keyed by work item, events become work_item.* (with linked, unlinked and landed) and work.migrated, and #N references follow one shared lookup order (work::refs). The TypeScript mirrors follow.
- G1T-Session:
- ses_01m561b3rje8nvxjcdsz6frggj
20 files+2272−9210/20 viewed
| 54 | 54 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] | |
| 55 | 55 | #[serde(rename_all = "snake_case")] | |
| 56 | 56 | pub enum RepoRole { | |
| 57 | − | /// Read and clone; open issues and pull requests, and comment. | |
| 57 | + | /// Read and clone; open work items and pull requests, and comment. | |
| 58 | 58 | Read, | |
| 59 | − | /// Read, and manage issues and pull requests: label, assign, close. | |
| 59 | + | /// Read, and manage work items and pull requests: label, assign, close. | |
| 60 | 60 | Triage, | |
| 61 | 61 | /// Triage, and push, merge, and put agents to work. | |
| 62 | 62 | Write, | |
| ⋯ | |||
| 165 | 165 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] | |
| 166 | 166 | #[serde(rename_all = "snake_case")] | |
| 167 | 167 | pub enum Capability { | |
| 168 | − | /// See the code, issues and pull requests; clone and fetch. | |
| 168 | + | /// See the code, work items and pull requests; clone and fetch. | |
| 169 | 169 | Read, | |
| 170 | − | /// Open issues and pull requests, and comment on them. | |
| 170 | + | /// Open work items and pull requests, and comment on them. | |
| 171 | 171 | Participate, | |
| 172 | − | /// Apply labels and milestones; assign, close and reopen issues and | |
| 173 | − | /// pull requests; ask for reviews. | |
| 172 | + | /// Apply labels and milestones; assign, close and reopen work items | |
| 173 | + | /// and pull requests; ask for reviews. | |
| 174 | 174 | Triage, | |
| 175 | 175 | /// Push to branches that are not protected, and edit files on the web. | |
| 176 | 176 | Push, | |
| ⋯ | |||
| 245 | 245 | /// follows GitHub's table of repository roles; where g1t differs, the | |
| 246 | 246 | /// access guide says so. | |
| 247 | 247 | pub const CAPABILITIES: [CapabilityRow; 16] = [ | |
| 248 | − | CapabilityRow { capability: Capability::Read, role: RepoRole::Read, about: "See code, issues and pull requests; clone and fetch" }, | |
| 249 | − | CapabilityRow { capability: Capability::Participate, role: RepoRole::Read, about: "Open issues and pull requests, and comment" }, | |
| 250 | − | CapabilityRow { capability: Capability::Triage, role: RepoRole::Triage, about: "Apply labels and milestones; assign, close and reopen issues and pull requests" }, | |
| 248 | + | CapabilityRow { capability: Capability::Read, role: RepoRole::Read, about: "See code, work items and pull requests; clone and fetch" }, | |
| 249 | + | CapabilityRow { capability: Capability::Participate, role: RepoRole::Read, about: "Open work items and pull requests, and comment" }, | |
| 250 | + | CapabilityRow { capability: Capability::Triage, role: RepoRole::Triage, about: "Apply labels and milestones; assign, close and reopen work items and pull requests" }, | |
| 251 | 251 | CapabilityRow { capability: Capability::Push, role: RepoRole::Write, about: "Push to branches that are not protected" }, | |
| 252 | 252 | CapabilityRow { capability: Capability::Merge, role: RepoRole::Write, about: "Merge pull requests and use the merge queue" }, | |
| 253 | 253 | CapabilityRow { capability: Capability::ManageLabels, role: RepoRole::Write, about: "Create, edit and delete labels and milestones" }, | |
| ⋯ | |||
| 462 | 462 | } | |
| 463 | 463 | } | |
| 464 | 464 | ||
| 465 | + | // --- Work items ------------------------------------------------------------- | |
| 466 | + | ||
| 467 | + | /// What a membership of `workspace` gives on its work items: what it gives | |
| 468 | + | /// on its repositories, and at least reading and taking part, since every | |
| 469 | + | /// member sees the workspace's work. A narrowed token that does not reach | |
| 470 | + | /// the whole workspace gives none. | |
| 471 | + | pub fn workspace_role(user: &User, workspace: &str) -> Option<RepoRole> { | |
| 472 | + | if user.token.as_deref().is_some_and(|token| !token.covers_workspace(workspace)) { | |
| 473 | + | return None; | |
| 474 | + | } | |
| 475 | + | let membership = user.workspaces.iter().find(|membership| membership.slug.eq_ignore_ascii_case(workspace))?; | |
| 476 | + | membership_role(user, membership).max(Some(RepoRole::Read)) | |
| 477 | + | } | |
| 478 | + | ||
| 479 | + | /// The viewer's role on a work item of `workspace` filed from `origin` | |
| 480 | + | /// (none for one filed in the workspace itself): the better of what their | |
| 481 | + | /// membership of the workspace gives ([`workspace_role`]) and their role | |
| 482 | + | /// on the origin ([`permission`]). So members see all of a workspace's | |
| 483 | + | /// work items, anyone who can read a repository sees those filed there | |
| 484 | + | /// (on a public one, everyone), and one with no origin is for members | |
| 485 | + | /// only. `None` means they may not see it at all. | |
| 486 | + | pub fn item_role(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>) -> Option<RepoRole> { | |
| 487 | + | let member = viewer.and_then(|user| workspace_role(user, workspace)); | |
| 488 | + | let repo = origin.and_then(|origin| permission(viewer, origin)); | |
| 489 | + | member.max(repo) | |
| 490 | + | } | |
| 491 | + | ||
| 492 | + | /// Whether the viewer may do `capability` on a work item, by the same | |
| 493 | + | /// table as [`can`]: through their membership of the workspace, or their | |
| 494 | + | /// role on its origin as [`can`] reads it. | |
| 495 | + | pub fn can_item(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>, capability: Capability) -> bool { | |
| 496 | + | let member = viewer.and_then(|user| workspace_role(user, workspace)); | |
| 497 | + | if member.is_some_and(|role| allows(role, capability)) { | |
| 498 | + | return true; | |
| 499 | + | } | |
| 500 | + | origin.is_some_and(|origin| can(viewer, origin, capability)) | |
| 501 | + | } | |
| 502 | + | ||
| 503 | + | /// `Ok` when the viewer may do `capability` on a work item; otherwise | |
| 504 | + | /// whether to answer not found (they may not see it) or forbidden. | |
| 505 | + | pub fn check_item(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>, capability: Capability) -> Result<(), Denied> { | |
| 506 | + | if item_role(viewer, workspace, origin).is_none() { | |
| 507 | + | return Err(Denied::NotFound); | |
| 508 | + | } | |
| 509 | + | if can_item(viewer, workspace, origin, capability) { | |
| 510 | + | Ok(()) | |
| 511 | + | } else { | |
| 512 | + | Err(Denied::Forbidden) | |
| 513 | + | } | |
| 514 | + | } | |
| 515 | + | ||
| 516 | + | /// The sentence a refusal of `capability` on a work item of `workspace` | |
| 517 | + | /// gives. | |
| 518 | + | pub fn needs_on_item(capability: Capability, workspace: &str) -> String { | |
| 519 | + | format!("You need the {} role or higher in {workspace} to do that.", least_role(capability).label()) | |
| 520 | + | } | |
| 521 | + | ||
| 465 | 522 | /// The sentence a refusal of `capability` gives. | |
| 466 | 523 | pub fn needs(capability: Capability, repo: &str) -> String { | |
| 467 | 524 | let role = least_role(capability); | |
| ⋯ | |||
| 766 | 823 | } | |
| 767 | 824 | ||
| 768 | 825 | #[cfg(test)] | |
| 826 | + | mod item_tests { | |
| 827 | + | use super::*; | |
| 828 | + | use crate::scopes::{RepositorySelection, TokenAccess, TokenReach}; | |
| 829 | + | ||
| 830 | + | fn person(memberships: &[(&str, Role, Option<BasePermission>)], grants: &[(&str, &str, RepoRole)]) -> User { | |
| 831 | + | User { | |
| 832 | + | id: "usr_1".into(), | |
| 833 | + | username: "ana".into(), | |
| 834 | + | verified: true, | |
| 835 | + | workspaces: memberships | |
| 836 | + | .iter() | |
| 837 | + | .map(|(slug, role, base)| Membership { role: *role, base_permission: *base, ..Membership::member(*slug) }) | |
| 838 | + | .collect(), | |
| 839 | + | grants: grants | |
| 840 | + | .iter() | |
| 841 | + | .map(|(id, workspace, role)| RepoGrant { repo_id: (*id).into(), workspace: (*workspace).into(), role: *role, team: None }) | |
| 842 | + | .collect(), | |
| 843 | + | ..User::default() | |
| 844 | + | } | |
| 845 | + | } | |
| 846 | + | ||
| 847 | + | fn origin(private: bool) -> Option<RepoRef<'static>> { | |
| 848 | + | Some(RepoRef { id: "rep_1", namespace: "acme", private }) | |
| 849 | + | } | |
| 850 | + | ||
| 851 | + | #[test] | |
| 852 | + | fn members_see_every_work_item_of_their_workspace() { | |
| 853 | + | let member = person(&[("acme", Role::Member, Some(BasePermission::None))], &[]); | |
| 854 | + | assert_eq!(item_role(Some(&member), "acme", None), Some(RepoRole::Read)); | |
| 855 | + | assert_eq!(item_role(Some(&member), "acme", origin(true)), Some(RepoRole::Read)); | |
| 856 | + | assert!(can_item(Some(&member), "acme", None, Capability::Participate)); | |
| 857 | + | assert!(!can_item(Some(&member), "acme", None, Capability::Triage)); | |
| 858 | + | let writer = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]); | |
| 859 | + | assert!(can_item(Some(&writer), "ACME", None, Capability::ManageLabels)); | |
| 860 | + | let owner = person(&[("acme", Role::Owner, None)], &[]); | |
| 861 | + | assert_eq!(item_role(Some(&owner), "acme", None), Some(RepoRole::Admin)); | |
| 862 | + | // Another workspace's member sees none of them. | |
| 863 | + | let other = person(&[("globex", Role::Owner, None)], &[]); | |
| 864 | + | assert_eq!(check_item(Some(&other), "acme", None, Capability::Read), Err(Denied::NotFound)); | |
| 865 | + | } | |
| 866 | + | ||
| 867 | + | #[test] | |
| 868 | + | fn anyone_who_can_read_the_origin_sees_what_was_filed_there() { | |
| 869 | + | // A public repository's work items are public, and anyone signed | |
| 870 | + | // in may take part in them, as in the repository. | |
| 871 | + | assert_eq!(item_role(None, "acme", origin(false)), Some(RepoRole::Read)); | |
| 872 | + | let outsider = person(&[], &[]); | |
| 873 | + | assert!(can_item(Some(&outsider), "acme", origin(false), Capability::Participate)); | |
| 874 | + | assert_eq!(check_item(Some(&outsider), "acme", origin(false), Capability::Triage), Err(Denied::Forbidden)); | |
| 875 | + | // A private one's are for those it is shared with. | |
| 876 | + | assert_eq!(item_role(Some(&outsider), "acme", origin(true)), None); | |
| 877 | + | let collaborator = person(&[], &[("rep_1", "acme", RepoRole::Triage)]); | |
| 878 | + | assert_eq!(item_role(Some(&collaborator), "acme", origin(true)), Some(RepoRole::Triage)); | |
| 879 | + | assert!(can_item(Some(&collaborator), "acme", origin(true), Capability::Triage)); | |
| 880 | + | // With no origin, only members. | |
| 881 | + | assert_eq!(item_role(Some(&collaborator), "acme", None), None); | |
| 882 | + | } | |
| 883 | + | ||
| 884 | + | #[test] | |
| 885 | + | fn a_narrowed_token_reaches_only_what_it_was_made_for() { | |
| 886 | + | let mut member = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]); | |
| 887 | + | member.token = Some(Box::new(TokenAccess { | |
| 888 | + | reach: Some(TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::Selected, repo_ids: vec!["rep_2".into()] }), | |
| 889 | + | ..TokenAccess::full() | |
| 890 | + | })); | |
| 891 | + | // Not the whole workspace: no member's view of it. | |
| 892 | + | assert_eq!(item_role(Some(&member), "acme", None), None); | |
| 893 | + | // A public origin still reads. | |
| 894 | + | assert_eq!(item_role(Some(&member), "acme", origin(false)), Some(RepoRole::Read)); | |
| 895 | + | member.token = Some(Box::new(TokenAccess { | |
| 896 | + | reach: Some(TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::All, repo_ids: Vec::new() }), | |
| 897 | + | ..TokenAccess::full() | |
| 898 | + | })); | |
| 899 | + | assert_eq!(item_role(Some(&member), "acme", None), Some(RepoRole::Write)); | |
| 900 | + | } | |
| 901 | + | } | |
| 902 | + | ||
| 903 | + | #[cfg(test)] | |
| 769 | 904 | mod tests { | |
| 770 | 905 | use super::*; | |
| 771 | 906 | ||
| 1 | − | //! Starting work on an issue in a session of its own, on the person's own | |
| 1 | + | //! Starting work on a work item in a session of its own, on the person's own | |
| 2 | 2 | //! Claude (the integrations service, `src/agent_sessions.rs`). | |
| 3 | 3 | //! | |
| 4 | − | //! g1t runs no model. Work on an issue happens in someone's own harness: | |
| 4 | + | //! g1t runs no model. Work on a work item happens in someone's own harness: | |
| 5 | 5 | //! their Claude Code with the g1t plugin, or a Claude Managed Agents | |
| 6 | 6 | //! session that g1t starts with the workspace's own Anthropic key. Either | |
| 7 | 7 | //! way the session is recorded on g1t (`work::Session`) and tied to the | |
| 8 | − | //! issue and the pull requests it makes. `start_agent_session` is what | |
| 9 | − | //! Ask, chat and the issue page call to hand work off. | |
| 8 | + | //! work item and the pull requests it makes. `start_agent_session` is what | |
| 9 | + | //! Ask, chat and the work item page call to hand work off. | |
| 10 | 10 | ||
| 11 | 11 | use serde::{Deserialize, Serialize}; | |
| 12 | 12 | ||
| ⋯ | |||
| 18 | 18 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 19 | 19 | #[serde(rename_all = "snake_case")] | |
| 20 | 20 | pub enum AgentMode { | |
| 21 | − | /// Work out how, and say so on the issue; change nothing. | |
| 21 | + | /// Work out how, and say so on the work item; change nothing. | |
| 22 | 22 | Plan, | |
| 23 | 23 | /// Make the change and open a pull request. | |
| 24 | 24 | #[default] | |
| 25 | 25 | Implement, | |
| 26 | − | /// Review the issue's pull request. | |
| 26 | + | /// Review the work item's pull request. | |
| 27 | 27 | Review, | |
| 28 | 28 | } | |
| 29 | 29 | ||
| ⋯ | |||
| 49 | 49 | /// The most characters of an instruction kept. | |
| 50 | 50 | pub const MAX_INSTRUCTION_CHARS: usize = 4_000; | |
| 51 | 51 | ||
| 52 | − | /// Who asked g1t in an issue or a pull request, rather than from a | |
| 53 | − | /// button: by assigning it the issue, or by mentioning `@g1t` in a comment. | |
| 52 | + | /// Who asked g1t in a work item or a pull request, rather than from a | |
| 53 | + | /// button: by assigning it the work item, or by mentioning `@g1t` in a comment. | |
| 54 | 54 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 55 | 55 | pub struct Asked { | |
| 56 | 56 | /// Their username. | |
| ⋯ | |||
| 58 | 58 | /// What their comment said; none for an assignment. | |
| 59 | 59 | #[serde(default)] | |
| 60 | 60 | pub comment: Option<String>, | |
| 61 | − | /// Where they asked: the comment, or the issue. | |
| 61 | + | /// Where they asked: the comment, or the work item. | |
| 62 | 62 | pub url: String, | |
| 63 | 63 | } | |
| 64 | 64 | ||
| 65 | − | /// `start_agent_session`: work on `issue` (or a new issue from `title` and | |
| 65 | + | /// `start_agent_session`: work on `item` (or a new work item from `title` and | |
| 66 | 66 | /// `body`), or carry on with pull request `pull`, as `actor`. Returns | |
| 67 | 67 | /// `Outcome<AgentSessionStarted>`. | |
| 68 | 68 | #[derive(Debug, Serialize, Deserialize)] | |
| ⋯ | |||
| 70 | 70 | pub actor: User, | |
| 71 | 71 | pub repo: RepoPath, | |
| 72 | 72 | #[serde(default)] | |
| 73 | − | pub issue: Option<u32>, | |
| 74 | − | /// A pull request to carry on with, on its branch: in place of `issue`. | |
| 73 | + | pub item: Option<u32>, | |
| 74 | + | /// A pull request to carry on with, on its branch: in place of `item`. | |
| 75 | 75 | #[serde(default)] | |
| 76 | 76 | pub pull: Option<u32>, | |
| 77 | 77 | /// Set when g1t was asked in the thread (integrations' summon.rs). | |
| 78 | 78 | #[serde(default)] | |
| 79 | 79 | pub asked: Option<Asked>, | |
| 80 | − | /// Without `issue`: a new issue's title, opened first. | |
| 80 | + | /// Without `item`: a new work item's title, opened first. | |
| 81 | 81 | #[serde(default)] | |
| 82 | 82 | pub title: Option<String>, | |
| 83 | 83 | #[serde(default)] | |
| 84 | 84 | pub body: Option<String>, | |
| 85 | − | /// What to do beyond what the issue says. | |
| 85 | + | /// What to do beyond what the work item says. | |
| 86 | 86 | #[serde(default)] | |
| 87 | 87 | pub instruction: Option<String>, | |
| 88 | 88 | #[serde(default)] | |
| ⋯ | |||
| 98 | 98 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 99 | 99 | #[serde(rename_all = "camelCase")] | |
| 100 | 100 | pub struct AgentSessionStarted { | |
| 101 | − | /// The issue it works on: for a pull request with none, its number. | |
| 102 | − | pub issue: u32, | |
| 101 | + | /// The work item it works on, by number in the workspace: for a pull | |
| 102 | + | /// request with none, the pull request's number. | |
| 103 | + | pub item: u32, | |
| 103 | 104 | /// The pull request it carries on with, when it was started on one. | |
| 104 | 105 | #[serde(default)] | |
| 105 | 106 | pub pull: Option<u32>, | |
| 86 | 86 | #[serde(rename_all = "camelCase")] | |
| 87 | 87 | pub struct Decision { | |
| 88 | 88 | pub id: String, | |
| 89 | − | /// The issue it is about, when known. | |
| 90 | − | pub issue: Option<u32>, | |
| 89 | + | /// The work item it is about, when known, by number in its workspace. | |
| 90 | + | pub item: Option<u32>, | |
| 91 | 91 | /// The pull request it is about, when known. | |
| 92 | 92 | pub pull: Option<u32>, | |
| 93 | 93 | /// The session it was recorded in, if one was named. | |
| ⋯ | |||
| 124 | 124 | pub session_id: Option<String>, | |
| 125 | 125 | } | |
| 126 | 126 | ||
| 127 | − | /// `list_decisions`: the decisions on an issue (and on its pull requests) | |
| 128 | − | /// or on a pull request, oldest first. Returns `Outcome<Vec<Decision>>`. | |
| 127 | + | /// `list_decisions`: the decisions on a thread, oldest first: with `repo`, | |
| 128 | + | /// on its pull request `number`; without, on the work item `number` of | |
| 129 | + | /// `workspace` and on its pull requests. Returns `Outcome<Vec<Decision>>`. | |
| 129 | 130 | #[derive(Debug, Serialize, Deserialize)] | |
| 130 | 131 | #[serde(rename_all = "camelCase")] | |
| 131 | 132 | pub struct ListDecisionsArgs { | |
| 132 | − | pub repo: RepoPath, | |
| 133 | + | #[serde(default)] | |
| 134 | + | pub repo: Option<RepoPath>, | |
| 135 | + | #[serde(default)] | |
| 136 | + | pub workspace: String, | |
| 133 | 137 | pub viewer: Viewer, | |
| 134 | − | /// An issue's or a pull request's number: they share a sequence. | |
| 135 | 138 | pub number: u32, | |
| 136 | 139 | } | |
| 137 | 140 | ||
| ⋯ | |||
| 161 | 164 | /// The responsible pull request, as `pull`. | |
| 162 | 165 | pub number: u32, | |
| 163 | 166 | pub pull: u32, | |
| 164 | − | /// Its issue, if it has one. | |
| 165 | − | pub issue: Option<u32>, | |
| 167 | + | /// Its work item, if it has one. | |
| 168 | + | pub item: Option<u32>, | |
| 166 | 169 | /// The pull requests tested together, in queue order; the last is | |
| 167 | 170 | /// `pull`. | |
| 168 | 171 | pub combination: Vec<u32>, | |
| ⋯ | |||
| 192 | 195 | pub pull_id: String, | |
| 193 | 196 | pub number: u32, | |
| 194 | 197 | pub pull: u32, | |
| 195 | − | pub issue: Option<u32>, | |
| 198 | + | pub item: Option<u32>, | |
| 196 | 199 | pub combination: Vec<u32>, | |
| 197 | 200 | pub commit: Option<String>, | |
| 198 | 201 | } | |
| ⋯ | |||
| 202 | 205 | #[serde(rename_all = "camelCase")] | |
| 203 | 206 | pub struct DecisionRecorded { | |
| 204 | 207 | pub repo_id: String, | |
| 205 | − | /// The pull request it is about, or else its issue. | |
| 208 | + | /// The pull request it is about, or else its work item. | |
| 206 | 209 | pub number: u32, | |
| 207 | − | pub issue: Option<u32>, | |
| 210 | + | pub item: Option<u32>, | |
| 208 | 211 | pub decision: Decision, | |
| 209 | 212 | } | |
| 210 | 213 | ||
| ⋯ | |||
| 220 | 223 | pub merged_at: Option<String>, | |
| 221 | 224 | } | |
| 222 | 225 | ||
| 223 | − | /// The issue that pull request was for. | |
| 226 | + | /// The work item that pull request was for. | |
| 224 | 227 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 225 | 228 | #[serde(rename_all = "camelCase")] | |
| 226 | − | pub struct WhyIssue { | |
| 229 | + | pub struct WhyItem { | |
| 230 | + | pub workspace: String, | |
| 227 | 231 | pub number: u32, | |
| 228 | 232 | pub title: String, | |
| 229 | 233 | pub state: String, | |
| ⋯ | |||
| 271 | 275 | } | |
| 272 | 276 | ||
| 273 | 277 | /// `why`: why a line is the way it is. The commit that last changed it, | |
| 274 | − | /// the merged pull request it arrived in, that pull request's issue, the | |
| 278 | + | /// the merged pull request it arrived in, that pull request's work item, the | |
| 275 | 279 | /// decisions recorded on them, and the session entries behind the commit. | |
| 276 | 280 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 277 | 281 | #[serde(rename_all = "camelCase")] | |
| ⋯ | |||
| 280 | 284 | pub line: u32, | |
| 281 | 285 | pub commit: WhyCommit, | |
| 282 | 286 | pub pull: Option<WhyPull>, | |
| 283 | − | pub issue: Option<WhyIssue>, | |
| 287 | + | pub item: Option<WhyItem>, | |
| 284 | 288 | pub decisions: Vec<Decision>, | |
| 285 | 289 | pub sessions: Vec<SessionEntry>, | |
| 286 | 290 | } | |
| ⋯ | |||
| 329 | 333 | } | |
| 330 | 334 | ||
| 331 | 335 | #[test] | |
| 332 | − | fn an_integration_failure_carries_number_and_issue_for_filters() { | |
| 336 | + | fn an_integration_failure_carries_number_and_item_for_filters() { | |
| 333 | 337 | let event = IntegrationFailed { | |
| 334 | 338 | repo_id: "rep_1".into(), | |
| 335 | 339 | pull_id: "pr_1".into(), | |
| 336 | 340 | number: 14, | |
| 337 | 341 | pull: 14, | |
| 338 | − | issue: Some(9), | |
| 342 | + | item: Some(9), | |
| 339 | 343 | combination: vec![11, 12, 14], | |
| 340 | 344 | check: FailedCheck { name: "CI / merge_group".into(), conclusion: "failure".into(), url: None }, | |
| 341 | 345 | bisected: true, | |
| ⋯ | |||
| 346 | 350 | }; | |
| 347 | 351 | let sent = serde_json::to_value(&event).unwrap(); | |
| 348 | 352 | assert_eq!(sent["number"], 14); | |
| 349 | − | assert_eq!(sent["issue"], 9); | |
| 353 | + | assert_eq!(sent["item"], 9); | |
| 350 | 354 | assert_eq!(sent["otherPulls"], serde_json::json!([11, 12])); | |
| 351 | 355 | assert_eq!(sent["check"]["conclusion"], "failure"); | |
| 352 | 356 | assert_eq!(sent["landed"], serde_json::json!([])); | |
| 160 | 160 | pub caused_by_job: Option<String>, | |
| 161 | 161 | } | |
| 162 | 162 | ||
| 163 | − | /// The payload of `issue.opened`, `issue.updated`, `issue.assigned`, | |
| 164 | − | /// `issue.labeled`, `issue.unlabeled`, `issue.milestoned`, | |
| 165 | − | /// `issue.demilestoned`, `issue.closed` and `issue.reopened`; each uses | |
| 166 | − | /// the fields that apply to it. | |
| 163 | + | /// The work item events the work service publishes. Each one's payload is | |
| 164 | + | /// a [`WorkItemEvent`]; the event's `repoId` is the work item's origin, | |
| 165 | + | /// when it has one. | |
| 166 | + | pub const WORK_ITEM_EVENTS: [&str; 12] = [ | |
| 167 | + | "work_item.opened", | |
| 168 | + | "work_item.updated", | |
| 169 | + | "work_item.assigned", | |
| 170 | + | "work_item.labeled", | |
| 171 | + | "work_item.unlabeled", | |
| 172 | + | "work_item.milestoned", | |
| 173 | + | "work_item.demilestoned", | |
| 174 | + | "work_item.closed", | |
| 175 | + | "work_item.reopened", | |
| 176 | + | "work_item.linked", | |
| 177 | + | "work_item.unlinked", | |
| 178 | + | "work_item.landed", | |
| 179 | + | ]; | |
| 180 | + | ||
| 181 | + | /// The payload of every `work_item.*` event ([`WORK_ITEM_EVENTS`]); each | |
| 182 | + | /// uses the fields that apply to it. A repository's subscribers hear of | |
| 183 | + | /// work items linked to it (`repo_ids`); a workspace's, of all of its. | |
| 167 | 184 | #[derive(Debug, Default, Serialize)] | |
| 168 | 185 | #[serde(rename_all = "camelCase")] | |
| 169 | − | pub struct IssueEvent { | |
| 170 | − | pub issue_id: String, | |
| 171 | − | pub repo_id: String, | |
| 186 | + | pub struct WorkItemEvent { | |
| 187 | + | pub item_id: String, | |
| 188 | + | /// The workspace's slug. | |
| 189 | + | pub workspace: String, | |
| 172 | 190 | pub number: u32, | |
| 191 | + | /// Every repository it is linked to, its origin first. | |
| 192 | + | pub repo_ids: Vec<String>, | |
| 173 | 193 | /// Who opened it: g1t, for one its agent filed while at work. | |
| 174 | 194 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 175 | 195 | pub author: Option<crate::credentials::Principal>, | |
| 176 | − | /// For an issue g1t's agent filed: the person it was working for. | |
| 196 | + | /// For one g1t's agent filed: the person it was working for. | |
| 177 | 197 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 178 | 198 | pub requested_by: Option<crate::credentials::Principal>, | |
| 179 | 199 | #[serde(skip_serializing_if = "Option::is_none")] | |
| ⋯ | |||
| 181 | 201 | /// On close: `completed` or `not_planned`. | |
| 182 | 202 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 183 | 203 | pub reason: Option<&'static str>, | |
| 184 | − | /// On close: the number of the pull request whose merge closed it. | |
| 204 | + | /// On `work_item.landed` and on a close a landing caused: what landed, | |
| 205 | + | /// in which repository. | |
| 185 | 206 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 186 | − | pub resolved_by: Option<u32>, | |
| 187 | − | /// On close: the commit pushed to the default branch whose message | |
| 188 | − | /// closed it (`Fixes #12`), when one did. | |
| 207 | + | pub resolution: Option<crate::work::Resolution>, | |
| 208 | + | /// On `work_item.landed` and on close: the targets it has not landed | |
| 209 | + | /// in yet, by path. Empty on close. | |
| 189 | 210 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 190 | − | pub commit: Option<String>, | |
| 211 | + | pub missing: Option<Vec<String>>, | |
| 191 | 212 | /// On close: the session it was closed in, when it was: the one the | |
| 192 | 213 | /// closing commit names (`G1t-Session: ses_…`), or the caller's own | |
| 193 | − | /// session on the issue when it closed it with a token. | |
| 214 | + | /// session on the work item when it closed it with a token. | |
| 194 | 215 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 195 | 216 | pub session: Option<String>, | |
| 196 | − | /// On `issue.assigned`: the people it is now assigned to. | |
| 217 | + | /// On `work_item.assigned`: the people it is now assigned to. | |
| 197 | 218 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 198 | 219 | pub assignees: Option<Vec<String>>, | |
| 199 | − | /// On `issue.assigned`: those of them who were not before. | |
| 220 | + | /// On `work_item.assigned`: those of them who were not before. | |
| 200 | 221 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 201 | 222 | pub added: Option<Vec<String>>, | |
| 202 | − | /// On `issue.labeled` and `issue.unlabeled`: the label put on or taken | |
| 203 | − | /// off. One event for each. | |
| 223 | + | /// On `work_item.labeled` and `work_item.unlabeled`: the label put on | |
| 224 | + | /// or taken off. One event for each. | |
| 204 | 225 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 205 | 226 | pub label: Option<EventLabel>, | |
| 206 | − | /// On `issue.milestoned`: the milestone it was put in; on | |
| 207 | − | /// `issue.demilestoned`, the one it was taken out of. | |
| 227 | + | /// On `work_item.milestoned`: the milestone it was put in; on | |
| 228 | + | /// `work_item.demilestoned`, the one it was taken out of. | |
| 208 | 229 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 209 | 230 | pub milestone: Option<crate::work::MilestoneRef>, | |
| 231 | + | /// On `work_item.linked` and `work_item.unlinked`: the repository | |
| 232 | + | /// (`owner/name`) and how it is linked now, or was. | |
| 233 | + | #[serde(skip_serializing_if = "Option::is_none")] | |
| 234 | + | pub repo: Option<String>, | |
| 235 | + | #[serde(skip_serializing_if = "Option::is_none")] | |
| 236 | + | pub role: Option<crate::work::LinkRole>, | |
| 210 | 237 | } | |
| 211 | 238 | ||
| 212 | − | /// A label, as `issue.labeled`, `pull.labeled` and their `unlabeled` say. | |
| 239 | + | /// `work.migrated`: a page of where repositories' issues went when they | |
| 240 | + | /// became work items, for services that stored issue numbers to rewrite | |
| 241 | + | /// them. Sent once per page by the migration job, then with `done`. | |
| 242 | + | #[derive(Clone, Debug, Default, Serialize, serde::Deserialize)] | |
| 243 | + | #[serde(rename_all = "camelCase")] | |
| 244 | + | pub struct WorkMigrated { | |
| 245 | + | /// Counts from 1. | |
| 246 | + | pub page: u32, | |
| 247 | + | pub items: Vec<crate::work::MigratedItem>, | |
| 248 | + | /// The last page. | |
| 249 | + | pub done: bool, | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | /// A label, as `work_item.labeled`, `pull.labeled` and their `unlabeled` | |
| 253 | + | /// say. | |
| 213 | 254 | #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)] | |
| 214 | 255 | pub struct EventLabel { | |
| 215 | 256 | pub name: String, | |
| ⋯ | |||
| 238 | 279 | /// For a change g1t made: the person who asked for it. | |
| 239 | 280 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 240 | 281 | pub requested_by: Option<crate::credentials::Principal>, | |
| 241 | − | /// The number of the issue it is for. | |
| 282 | + | /// The number of the work item it is for, in its workspace. | |
| 242 | 283 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 243 | − | pub issue: Option<u32>, | |
| 284 | + | pub item: Option<u32>, | |
| 244 | 285 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 245 | 286 | pub agent: Option<String>, | |
| 246 | 287 | /// On merge: the commit the branch now points to. On update, when | |
| ⋯ | |||
| 374 | 415 | pub triggered_by: String, | |
| 375 | 416 | } | |
| 376 | 417 | ||
| 377 | − | /// `checks.completed`: a run of an issue's acceptance checks against a pull | |
| 418 | + | /// `checks.completed`: a run of a work item's acceptance checks against a pull | |
| 378 | 419 | /// request finished. | |
| 379 | 420 | #[derive(Debug, Serialize)] | |
| 380 | 421 | #[serde(rename_all = "camelCase")] | |
| ⋯ | |||
| 411 | 452 | pub pull: Option<u32>, | |
| 412 | 453 | } | |
| 413 | 454 | ||
| 414 | − | /// `comment.created`. `number` is the issue or pull request commented on. | |
| 455 | + | /// Where a comment is, as the `comment.*` events say: a pull request of a | |
| 456 | + | /// repository (`repo_id`, `pull_id`), or a work item of a workspace | |
| 457 | + | /// (`workspace`, `item_id`, and the repositories it is linked to in | |
| 458 | + | /// `repo_ids`). `number` is the pull request's or the work item's. | |
| 415 | 459 | #[derive(Debug, Default, Serialize)] | |
| 416 | 460 | #[serde(rename_all = "camelCase")] | |
| 417 | − | pub struct CommentCreated { | |
| 418 | − | pub comment_id: String, | |
| 419 | − | pub repo_id: String, | |
| 461 | + | pub struct CommentOn { | |
| 462 | + | /// The pull request's repository; a work item's origin, if any. | |
| 463 | + | #[serde(skip_serializing_if = "Option::is_none")] | |
| 464 | + | pub repo_id: Option<String>, | |
| 420 | 465 | pub number: u32, | |
| 421 | 466 | /// Set when the comment is on a pull request. | |
| 422 | 467 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 423 | 468 | pub pull_id: Option<String>, | |
| 469 | + | /// Set when the comment is on a work item. | |
| 470 | + | #[serde(skip_serializing_if = "Option::is_none")] | |
| 471 | + | pub item_id: Option<String>, | |
| 472 | + | #[serde(skip_serializing_if = "Option::is_none")] | |
| 473 | + | pub workspace: Option<String>, | |
| 474 | + | #[serde(skip_serializing_if = "Vec::is_empty")] | |
| 475 | + | pub repo_ids: Vec<String>, | |
| 476 | + | } | |
| 477 | + | ||
| 478 | + | /// `comment.created`. | |
| 479 | + | #[derive(Debug, Default, Serialize)] | |
| 480 | + | #[serde(rename_all = "camelCase")] | |
| 481 | + | pub struct CommentCreated { | |
| 482 | + | pub comment_id: String, | |
| 483 | + | #[serde(flatten)] | |
| 484 | + | pub on: CommentOn, | |
| 424 | 485 | /// Set when the comment is a review: approve or request changes. | |
| 425 | 486 | #[serde(skip_serializing_if = "Option::is_none")] | |
| 426 | 487 | pub verdict: Option<crate::work::Verdict>, | |
| ⋯ | |||
| 442 | 503 | #[serde(rename_all = "camelCase")] | |
| 443 | 504 | pub struct CommentEdited { | |
| 444 | 505 | pub comment_id: String, | |
| 445 | − | pub repo_id: String, | |
| 446 | − | pub number: u32, | |
| 447 | − | /// Set when the comment is on a pull request. | |
| 448 | − | #[serde(skip_serializing_if = "Option::is_none")] | |
| 449 | − | pub pull_id: Option<String>, | |
| 506 | + | #[serde(flatten)] | |
| 507 | + | pub on: CommentOn, | |
| 450 | 508 | pub changes: CommentChanges, | |
| 451 | 509 | } | |
| 452 | 510 | ||
| ⋯ | |||
| 468 | 526 | #[serde(rename_all = "camelCase")] | |
| 469 | 527 | pub struct CommentDeleted { | |
| 470 | 528 | pub comment_id: String, | |
| 471 | − | pub repo_id: String, | |
| 472 | − | pub number: u32, | |
| 473 | − | /// Set when the comment was on a pull request. | |
| 474 | − | #[serde(skip_serializing_if = "Option::is_none")] | |
| 475 | − | pub pull_id: Option<String>, | |
| 529 | + | #[serde(flatten)] | |
| 530 | + | pub on: CommentOn, | |
| 476 | 531 | pub comment: DeletedComment, | |
| 477 | 532 | } | |
| 478 | 533 | ||
| ⋯ | |||
| 663 | 718 | /// Only events by this account id. | |
| 664 | 719 | #[serde(default)] | |
| 665 | 720 | pub actor: Option<String>, | |
| 666 | − | /// Only events about these issues or pull requests: their `number`, or | |
| 667 | − | /// the `issue` a comment, review or link is on. All when empty. | |
| 721 | + | /// Only events about these work items or pull requests: their | |
| 722 | + | /// `number`, or the `item` a comment, review or link is on. All when | |
| 723 | + | /// empty. | |
| 668 | 724 | #[serde(default)] | |
| 669 | 725 | pub numbers: Vec<u32>, | |
| 670 | 726 | /// Only events at or after this RFC 3339 time. | |
| ⋯ | |||
| 828 | 884 | } | |
| 829 | 885 | ||
| 830 | 886 | /// `repo.archived` and `repo.unarchived`: a repository became read-only, | |
| 831 | − | /// or writable again. While archived, pushes are refused, issues and pull | |
| 832 | − | /// requests are locked, and agents and workflows do not run for it; its | |
| 833 | − | /// deployments keep serving. | |
| 887 | + | /// or writable again. While archived, pushes are refused, its pull | |
| 888 | + | /// requests are locked, no work items are filed from it, and agents and | |
| 889 | + | /// workflows do not run for it; its deployments keep serving. | |
| 834 | 890 | #[derive(Clone, Debug, Serialize, serde::Deserialize)] | |
| 835 | 891 | #[serde(rename_all = "camelCase")] | |
| 836 | 892 | pub struct RepoArchived { | |
| ⋯ | |||
| 1248 | 1304 | pub closed: Vec<ActorCount>, | |
| 1249 | 1305 | } | |
| 1250 | 1306 | ||
| 1251 | − | /// Issues opened and closed, each by who did it. | |
| 1307 | + | /// Work items opened and closed, each by who did it. | |
| 1252 | 1308 | #[derive(Debug, Default, Serialize, serde::Deserialize, PartialEq, Eq)] | |
| 1253 | − | pub struct IssueDigest { | |
| 1309 | + | pub struct ItemDigest { | |
| 1254 | 1310 | pub opened: Vec<ActorCount>, | |
| 1255 | 1311 | pub closed: Vec<ActorCount>, | |
| 1256 | 1312 | } | |
| ⋯ | |||
| 1291 | 1347 | pub repo_id: String, | |
| 1292 | 1348 | pub pushes: PushDigest, | |
| 1293 | 1349 | pub pulls: PullDigest, | |
| 1294 | − | pub issues: IssueDigest, | |
| 1350 | + | pub items: ItemDigest, | |
| 1295 | 1351 | /// Reviews: `review.completed` (g1t's), and a comment with a verdict (a person's). | |
| 1296 | 1352 | pub reviews: Vec<ActorCount>, | |
| 1297 | 1353 | /// Comments without a verdict; one by an agent as itself is the agent's. | |
| ⋯ | |||
| 1332 | 1388 | fn comment_edits_and_deletes_say_what_it_was() { | |
| 1333 | 1389 | let edited = CommentEdited { | |
| 1334 | 1390 | comment_id: "cmt_1".into(), | |
| 1335 | − | repo_id: "rep_1".into(), | |
| 1336 | − | number: 7, | |
| 1337 | − | pull_id: Some("pr_1".into()), | |
| 1391 | + | on: CommentOn { repo_id: Some("rep_1".into()), number: 7, pull_id: Some("pr_1".into()), ..CommentOn::default() }, | |
| 1338 | 1392 | changes: CommentChanges { body: ChangedFrom { from: "Before".into() } }, | |
| 1339 | 1393 | }; | |
| 1340 | 1394 | assert_eq!( | |
| ⋯ | |||
| 1344 | 1398 | "changes": { "body": { "from": "Before" } }, | |
| 1345 | 1399 | }) | |
| 1346 | 1400 | ); | |
| 1401 | + | // On a work item: its workspace and every repository it is linked to. | |
| 1347 | 1402 | let deleted = CommentDeleted { | |
| 1348 | 1403 | comment_id: "cmt_1".into(), | |
| 1349 | − | repo_id: "rep_1".into(), | |
| 1350 | − | number: 7, | |
| 1351 | − | pull_id: None, | |
| 1404 | + | on: CommentOn { | |
| 1405 | + | repo_id: Some("rep_1".into()), | |
| 1406 | + | number: 7, | |
| 1407 | + | item_id: Some("wki_1".into()), | |
| 1408 | + | workspace: Some("acme".into()), | |
| 1409 | + | repo_ids: vec!["rep_1".into(), "rep_2".into()], | |
| 1410 | + | ..CommentOn::default() | |
| 1411 | + | }, | |
| 1352 | 1412 | comment: DeletedComment { | |
| 1353 | 1413 | id: "cmt_1".into(), | |
| 1354 | 1414 | body: "Gone".into(), | |
| ⋯ | |||
| 1361 | 1421 | assert_eq!( | |
| 1362 | 1422 | serde_json::to_value(deleted).unwrap(), | |
| 1363 | 1423 | serde_json::json!({ | |
| 1364 | − | "commentId": "cmt_1", "repoId": "rep_1", "number": 7, | |
| 1424 | + | "commentId": "cmt_1", "repoId": "rep_1", "number": 7, "itemId": "wki_1", "workspace": "acme", | |
| 1425 | + | "repoIds": ["rep_1", "rep_2"], | |
| 1365 | 1426 | "comment": { | |
| 1366 | 1427 | "id": "cmt_1", "body": "Gone", "author": { "id": "usr_1", "username": "ana" }, | |
| 1367 | 1428 | "createdAt": "2026-10-08T00:00:00Z", "path": null, "line": null, | |
| 3 | 3 | //! The events service keeps it, beside the event log: as events arrive it | |
| 4 | 4 | //! works out who should hear of each (see `services/events/src/inbox.rs`) | |
| 5 | 5 | //! and why. Each person has one item per **thread**, the thing it is about | |
| 6 | − | //! (an issue, a pull request, a workflow on a branch, a deployment): new | |
| 6 | + | //! (a work item, a pull request, a workflow on a branch, a deployment): new | |
| 7 | 7 | //! activity on a thread brings its item back to the top, unread, and adds | |
| 8 | 8 | //! a line to its short history, rather than adding another item. Items are | |
| 9 | 9 | //! kept by username, which never changes. | |
| ⋯ | |||
| 35 | 35 | //! `inbox_update_settings` takes `UpdateInboxSettingsArgs` and returns | |
| 36 | 36 | //! `InboxSettings`. | |
| 37 | 37 | //! | |
| 38 | − | //! What an event is about (the issue or pull request, its people, the | |
| 38 | + | //! What an event is about (the work item or pull request, its people, the | |
| 39 | 39 | //! comment) comes from the work service's `inbox_subject`, which takes | |
| 40 | 40 | //! `InboxSubjectArgs` and returns `Option<InboxSubject>`. | |
| 41 | 41 | ||
| ⋯ | |||
| 187 | 187 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 188 | 188 | #[serde(rename_all = "lowercase")] | |
| 189 | 189 | pub enum SubjectKind { | |
| 190 | − | Issue, | |
| 190 | + | /// A work item. | |
| 191 | + | Item, | |
| 191 | 192 | Pull, | |
| 192 | 193 | /// A workflow's runs on one branch. | |
| 193 | 194 | Run, | |
| ⋯ | |||
| 196 | 197 | } | |
| 197 | 198 | ||
| 198 | 199 | impl SubjectKind { | |
| 199 | − | pub const ALL: [SubjectKind; 4] = [SubjectKind::Issue, SubjectKind::Pull, SubjectKind::Run, SubjectKind::Deploy]; | |
| 200 | + | pub const ALL: [SubjectKind; 4] = [SubjectKind::Item, SubjectKind::Pull, SubjectKind::Run, SubjectKind::Deploy]; | |
| 200 | 201 | ||
| 201 | 202 | pub fn as_str(self) -> &'static str { | |
| 202 | 203 | match self { | |
| 203 | − | SubjectKind::Issue => "issue", | |
| 204 | + | SubjectKind::Item => "item", | |
| 204 | 205 | SubjectKind::Pull => "pull", | |
| 205 | 206 | SubjectKind::Run => "run", | |
| 206 | 207 | SubjectKind::Deploy => "deploy", | |
| ⋯ | |||
| 235 | 236 | /// The workspace it happened in. | |
| 236 | 237 | pub workspace: Option<String>, | |
| 237 | 238 | pub subject: Option<SubjectKind>, | |
| 238 | − | /// The issue or pull request's number. | |
| 239 | + | /// The work item's or pull request's number. | |
| 239 | 240 | pub number: Option<u32>, | |
| 240 | 241 | /// Where it is on g1t.sh: a path such as `/acme/rocket/pull/12`. | |
| 241 | 242 | pub url: String, | |
| ⋯ | |||
| 432 | 433 | pub item: InboxItem, | |
| 433 | 434 | /// Newest first, at most [`MAX_ACTIVITY`]. | |
| 434 | 435 | pub activity: Vec<InboxActivity>, | |
| 435 | − | /// For an issue or pull request; absent for a run or a deployment, | |
| 436 | + | /// For a work item or pull request; absent for a run or a deployment, | |
| 436 | 437 | /// which nobody subscribes to. | |
| 437 | 438 | pub subscription: Option<ThreadSubscription>, | |
| 438 | 439 | } | |
| 439 | 440 | ||
| 440 | − | /// A person's subscription to an issue or pull request. | |
| 441 | + | /// A person's subscription to a work item or pull request. | |
| 441 | 442 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 442 | 443 | #[serde(rename_all = "camelCase")] | |
| 443 | 444 | pub struct ThreadSubscription { | |
| ⋯ | |||
| 450 | 451 | /// (`comment`), were mentioned (`mention`) or subscribed by hand | |
| 451 | 452 | /// (`manual`). Absent when they are not. | |
| 452 | 453 | pub reason: Option<Reason>, | |
| 453 | − | /// `owner/name`, and the issue or pull request's number. | |
| 454 | + | /// `owner/name` (a work item's workspace, for one), and the work | |
| 455 | + | /// item's or pull request's number. | |
| 454 | 456 | pub repo: Option<String>, | |
| 455 | 457 | pub number: Option<u32>, | |
| 456 | 458 | /// RFC 3339: when they last chose, or null if they never did. | |
| 457 | 459 | pub updated_at: Option<String>, | |
| 458 | 460 | } | |
| 459 | 461 | ||
| 460 | − | /// Which issue or pull request: by a thread's id, or by its repository and | |
| 462 | + | /// Which work item or pull request: by a thread's id, or by its repository and | |
| 461 | 463 | /// number. | |
| 462 | 464 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 463 | 465 | #[serde(rename_all = "camelCase")] | |
| ⋯ | |||
| 493 | 495 | /// Only what they take part in or are mentioned in: the default. | |
| 494 | 496 | #[default] | |
| 495 | 497 | Participating, | |
| 496 | − | /// Everything: every issue and pull request opened, commented on, | |
| 498 | + | /// Everything: every work item and pull request opened, commented on, | |
| 497 | 499 | /// closed or merged, and every deployment. | |
| 498 | 500 | All, | |
| 499 | 501 | /// Nothing at all, not even a mention. | |
| ⋯ | |||
| 657 | 659 | } | |
| 658 | 660 | ||
| 659 | 661 | /// `inbox_subject` on the work service: what an event names, for the | |
| 660 | − | /// inbox. A service-to-service read: it checks nobody's access, and what | |
| 661 | − | /// it returns is only ever shown to the people it names, or to those who | |
| 662 | − | /// can read the repository. | |
| 662 | + | /// inbox: a work item by `item_id` (from a `work_item.*` or comment | |
| 663 | + | /// event's data), else the pull request `number` of `repo_id`. A | |
| 664 | + | /// service-to-service read: it checks nobody's access, and what it returns | |
| 665 | + | /// is only ever shown to the people it names, or to those who can read it. | |
| 663 | 666 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 664 | 667 | #[serde(rename_all = "camelCase")] | |
| 665 | 668 | pub struct InboxSubjectArgs { | |
| 666 | − | pub repo_id: String, | |
| 669 | + | #[serde(default)] | |
| 670 | + | pub repo_id: Option<String>, | |
| 671 | + | #[serde(default)] | |
| 667 | 672 | pub number: u32, | |
| 673 | + | #[serde(default)] | |
| 674 | + | pub item_id: Option<String>, | |
| 668 | 675 | /// The comment the event is about, if any. | |
| 669 | 676 | #[serde(default)] | |
| 670 | 677 | pub comment_id: Option<String>, | |
| ⋯ | |||
| 673 | 680 | #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 674 | 681 | #[serde(rename_all = "camelCase")] | |
| 675 | 682 | pub struct InboxSubject { | |
| 676 | − | /// `issue` or `pull`. | |
| 683 | + | /// `item` or `pull`. | |
| 677 | 684 | pub kind: Option<SubjectKind>, | |
| 678 | 685 | pub title: String, | |
| 679 | 686 | pub author: Principal, | |
| ⋯ | |||
| 687 | 694 | #[serde(default)] | |
| 688 | 695 | pub reviewers: Vec<String>, | |
| 689 | 696 | /// The people its description mentions by name, outside code and | |
| 690 | − | /// quotes, for `issue.opened` and `pull.opened`. Never `g1t`. | |
| 697 | + | /// quotes, for `work_item.opened` and `pull.opened`. Never `g1t`. | |
| 691 | 698 | #[serde(default)] | |
| 692 | 699 | pub mentions: Vec<String>, | |
| 693 | 700 | /// The teams its description mentions, with the people each tells. | |
| 694 | 701 | #[serde(default)] | |
| 695 | 702 | pub team_mentions: Vec<TeamMentioned>, | |
| 696 | − | /// For a pull request: the issue it is for, with that issue's people. | |
| 703 | + | /// For a pull request: the work item it is for, with its people. | |
| 697 | 704 | #[serde(default)] | |
| 698 | − | pub issue: Option<Box<InboxSubject>>, | |
| 705 | + | pub item: Option<Box<InboxSubject>>, | |
| 699 | 706 | #[serde(default)] | |
| 700 | 707 | pub comment: Option<InboxComment>, | |
| 701 | 708 | } | |
| 36 | 36 | /// The full ref a push or deletion moved (`refs/heads/main`), or a run's. | |
| 37 | 37 | #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")] | |
| 38 | 38 | pub git_ref: Option<String>, | |
| 39 | − | /// The issue or pull request it is on, or the pull request a run ran for. | |
| 39 | + | /// The work item or pull request it is on, or the pull request a run ran for. | |
| 40 | 40 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 41 | 41 | pub number: Option<u32>, | |
| 42 | 42 | /// The commit: where a push left its ref, the one checked or reported on. | |
| ⋯ | |||
| 67 | 67 | "branch.renamed", | |
| 68 | 68 | "repo.default_branch_changed", | |
| 69 | 69 | "pull.", | |
| 70 | − | "issue.", | |
| 70 | + | "work_item.", | |
| 71 | 71 | "comment.", | |
| 72 | 72 | "review.", | |
| 73 | 73 | "checks.", | |
| ⋯ | |||
| 82 | 82 | "secret_scanning_alert.", | |
| 83 | 83 | "code_scanning_alert.", | |
| 84 | 84 | "vulnerability_alert.", | |
| 85 | − | // Coordinating work: the issue, pull request and Work pages show them. | |
| 85 | + | // Coordinating work: the work item, pull request and Work pages show them. | |
| 86 | 86 | "work.", | |
| 87 | 87 | "session.started", | |
| 88 | 88 | "session.ended", | |
| ⋯ | |||
| 139 | 139 | "check_suite" => change.sha = text(&data["checkSuite"]["headSha"]), | |
| 140 | 140 | "code_scanning_alert" => change.number = number(&data["alertNumber"]), | |
| 141 | 141 | "secret_scanning_alert" | "vulnerability_alert" | "release" | "queue" | "deployment" | "deployment_status" | "repo" | "branch" => {} | |
| 142 | − | // Issues, pull requests, comments and reviews name their number. | |
| 142 | + | // Work items, pull requests, comments and reviews name their number. | |
| 143 | 143 | _ => change.number = number(&data["number"]), | |
| 144 | 144 | } | |
| 145 | 145 | Some(change) | |
| ⋯ | |||
| 191 | 191 | } | |
| 192 | 192 | ||
| 193 | 193 | #[test] | |
| 194 | − | fn pull_requests_issues_comments_and_checks_name_their_number() { | |
| 195 | − | for kind in ["pull.updated", "pull.merged", "issue.closed", "comment.created", "review.completed"] { | |
| 194 | + | fn pull_requests_work_items_comments_and_checks_name_their_number() { | |
| 195 | + | for kind in ["pull.updated", "pull.merged", "work_item.closed", "comment.created", "review.completed"] { | |
| 196 | 196 | let change = change_of(&event(kind, Some("rep_1"), json!({ "number": 7, "title": "secret plans" }))).unwrap(); | |
| 197 | 197 | assert_eq!(change.number, Some(7), "{kind}"); | |
| 198 | 198 | } | |
| ⋯ | |||
| 219 | 219 | ||
| 220 | 220 | #[test] | |
| 221 | 221 | fn nothing_written_in_the_repository_goes_along() { | |
| 222 | − | let change = change_of(&event("issue.opened", Some("rep_1"), json!({ "number": 1, "title": "Leaked title", "body": "Leaked body" }))).unwrap(); | |
| 222 | + | let change = change_of(&event("work_item.opened", Some("rep_1"), json!({ "number": 1, "title": "Leaked title", "body": "Leaked body" }))).unwrap(); | |
| 223 | 223 | let sent = serde_json::to_string(&change).unwrap(); | |
| 224 | 224 | assert!(!sent.contains("Leaked")); | |
| 225 | − | assert_eq!(sent, r#"{"repo_id":"rep_1","kind":"issue.opened","number":1,"at":"2026-10-11T10:00:00Z"}"#); | |
| 225 | + | assert_eq!(sent, r#"{"repo_id":"rep_1","kind":"work_item.opened","number":1,"at":"2026-10-11T10:00:00Z"}"#); | |
| 226 | 226 | } | |
| 227 | 227 | ||
| 228 | 228 | #[test] | |
| ⋯ | |||
| 241 | 241 | } | |
| 242 | 242 | ||
| 243 | 243 | #[test] | |
| 244 | − | fn coordination_names_the_issue_or_pull_request_it_is_about() { | |
| 244 | + | fn coordination_names_the_work_item_or_pull_request_it_is_about() { | |
| 245 | 245 | for kind in ["work.claimed", "work.claim_expired", "session.started", "dependency.changed", "overlap.detected", "integration.failed", "integration.passed", "decision.recorded"] { | |
| 246 | − | let change = change_of(&event(kind, None, json!({ "repoId": "rep_1", "number": 12, "issue": 4 }))).unwrap(); | |
| 246 | + | let change = change_of(&event(kind, None, json!({ "repoId": "rep_1", "number": 12, "item": 4 }))).unwrap(); | |
| 247 | 247 | assert_eq!((change.repo_id.as_str(), change.number), ("rep_1", Some(12)), "{kind}"); | |
| 248 | 248 | } | |
| 249 | 249 | // Each recorded step of a session changes no page anyone is watching live. | |
| 913 | 913 | pub fn covers_repo(&self, repo_id: &str, namespace: &str) -> bool { | |
| 914 | 914 | self.reach.as_ref().is_none_or(|reach| reach.covers(repo_id, namespace)) | |
| 915 | 915 | } | |
| 916 | + | ||
| 917 | + | /// Whether it reaches the whole workspace `namespace`, as its work | |
| 918 | + | /// items, labels and milestones need: every token but a workflow job's | |
| 919 | + | /// (its one repository) and a narrowed one made for another workspace | |
| 920 | + | /// or for some of this one's repositories. | |
| 921 | + | pub fn covers_workspace(&self, namespace: &str) -> bool { | |
| 922 | + | self.repo.is_none() | |
| 923 | + | && self | |
| 924 | + | .reach | |
| 925 | + | .as_ref() | |
| 926 | + | .is_none_or(|reach| reach.owned_by(namespace) && reach.repositories == RepositorySelection::All) | |
| 927 | + | } | |
| 916 | 928 | } | |
| 917 | 929 | ||
| 918 | 930 | /// Every operation of the API and MCP server, with the scope it needs. An |
| 36 | 36 | "branch.renamed", | |
| 37 | 37 | "repo.created", | |
| 38 | 38 | "repo.archived", | |
| 39 | + | "repo.visibility_changed", | |
| 39 | 40 | "repo.default_branch_changed", | |
| 40 | 41 | "pull.merged", | |
| 41 | 42 | "comment.created", | |
| 42 | 43 | ], | |
| 43 | 44 | ), | |
| 44 | − | // Pull requests g1t is seeing through, mentions, queued issues and the | |
| 45 | + | // Pull requests g1t is seeing through, mentions, queued work items and the | |
| 45 | 46 | // merge queue (runner/src/index.ts queue). | |
| 46 | 47 | ( | |
| 47 | 48 | "SUBSCRIBER_RUNNER", | |
| ⋯ | |||
| 57 | 58 | "checks.completed", | |
| 58 | 59 | "queue.changed", | |
| 59 | 60 | "comment.created", | |
| 60 | − | "issue.opened", | |
| 61 | − | "issue.updated", | |
| 62 | − | "issue.closed", | |
| 61 | + | "work_item.opened", | |
| 62 | + | "work_item.updated", | |
| 63 | + | "work_item.closed", | |
| 63 | 64 | "repo.archived", | |
| 64 | 65 | ], | |
| 65 | 66 | ), | |
| 66 | 67 | // Write-back to linked trackers (integrations/src lib.rs), and g1t | |
| 67 | 68 | // taking on what it is assigned or asked in a thread (summon.rs). | |
| 68 | − | ("SUBSCRIBER_INTEGRATIONS", &["issue.closed", "pull.opened", "issue.assigned", "comment.created"]), | |
| 69 | + | // `work.migrated` rewrites links kept to issue numbers. | |
| 70 | + | ("SUBSCRIBER_INTEGRATIONS", &["work_item.closed", "pull.opened", "work_item.assigned", "comment.created", "work.migrated"]), | |
| 69 | 71 | // A hook may ask for any type, or for every one. | |
| 70 | 72 | ("SUBSCRIBER_WEBHOOKS", &["*"]), | |
| 71 | 73 | // Every event a workflow can run on (g1t_actions::events::github_events), | |
| ⋯ | |||
| 76 | 78 | "git.push", | |
| 77 | 79 | "git.deleted", | |
| 78 | 80 | "pull.*", | |
| 79 | − | "issue.*", | |
| 81 | + | "work_item.*", | |
| 80 | 82 | "comment.*", | |
| 81 | 83 | "release.*", | |
| 82 | 84 | "deployment.created", | |
| ⋯ | |||
| 112 | 114 | &[ | |
| 113 | 115 | "git.push", | |
| 114 | 116 | "repo.*", | |
| 115 | − | "issue.opened", | |
| 116 | − | "issue.closed", | |
| 117 | − | "issue.reopened", | |
| 117 | + | "work_item.opened", | |
| 118 | + | "work_item.closed", | |
| 119 | + | "work_item.reopened", | |
| 118 | 120 | "pull.opened", | |
| 119 | 121 | "pull.updated", | |
| 120 | 122 | "pull.merged", | |
| ⋯ | |||
| 163 | 165 | ], | |
| 164 | 166 | ), | |
| 165 | 167 | // The site-wide search index (search/src/index.rs). | |
| 166 | − | ("SUBSCRIBER_SEARCH", &["git.push", "repo.*", "issue.*", "pull.*", "user.*", "workspace.*"]), | |
| 168 | + | ("SUBSCRIBER_SEARCH", &["git.push", "repo.*", "work_item.*", "work.migrated", "pull.*", "user.*", "workspace.*"]), | |
| 167 | 169 | // Composer packages read from repositories, and packages that follow | |
| 168 | 170 | // their workspace, team or repository (packages/src/lib.rs on_event). | |
| 169 | 171 | ( | |
| ⋯ | |||
| 206 | 208 | use super::*; | |
| 207 | 209 | ||
| 208 | 210 | /// Types published on the bus that hooks are not offered. | |
| 209 | − | const UNOFFERED: [&str; 28] = [ | |
| 211 | + | const UNOFFERED: [&str; 29] = [ | |
| 212 | + | // The one-off move of issues into work items. | |
| 213 | + | "work.migrated", | |
| 210 | 214 | "pull.mergecheck", | |
| 211 | 215 | "pull.mergeability", | |
| 212 | 216 | "deployment.review_requested", | |
| ⋯ | |||
| 281 | 285 | assert!(!routed("SUBSCRIBER_ACTIONS", "check_run.completed")); | |
| 282 | 286 | assert!(routed("SUBSCRIBER_RUNNER", "comment.created")); | |
| 283 | 287 | assert!(!routed("SUBSCRIBER_RUNNER", "status.created")); | |
| 284 | − | assert!(routed("SUBSCRIBER_INTEGRATIONS", "issue.closed")); | |
| 285 | − | assert!(routed("SUBSCRIBER_INTEGRATIONS", "issue.assigned")); | |
| 288 | + | assert!(routed("SUBSCRIBER_INTEGRATIONS", "work_item.closed")); | |
| 289 | + | assert!(routed("SUBSCRIBER_INTEGRATIONS", "work_item.assigned")); | |
| 286 | 290 | assert!(routed("SUBSCRIBER_INTEGRATIONS", "comment.created")); | |
| 287 | 291 | assert!(!routed("SUBSCRIBER_INTEGRATIONS", "git.push")); | |
| 288 | 292 | assert!(!routed("SUBSCRIBER_INTEGRATIONS", "check_run.completed")); | |
| 15 | 15 | use crate::{User, Viewer}; | |
| 16 | 16 | ||
| 17 | 17 | /// Every event a webhook can be sent, in the order people are shown them. | |
| 18 | − | pub const EVENT_TYPES: [&str; 127] = [ | |
| 18 | + | pub const EVENT_TYPES: [&str; 130] = [ | |
| 19 | 19 | "git.push", | |
| 20 | 20 | "git.deleted", | |
| 21 | 21 | "branch.renamed", | |
| ⋯ | |||
| 48 | 48 | "ruleset.created", | |
| 49 | 49 | "ruleset.updated", | |
| 50 | 50 | "ruleset.deleted", | |
| 51 | − | "issue.opened", | |
| 52 | − | "issue.updated", | |
| 53 | − | "issue.assigned", | |
| 54 | − | "issue.labeled", | |
| 55 | − | "issue.unlabeled", | |
| 56 | − | "issue.milestoned", | |
| 57 | − | "issue.demilestoned", | |
| 58 | − | "issue.closed", | |
| 59 | − | "issue.reopened", | |
| 51 | + | "work_item.opened", | |
| 52 | + | "work_item.updated", | |
| 53 | + | "work_item.assigned", | |
| 54 | + | "work_item.labeled", | |
| 55 | + | "work_item.unlabeled", | |
| 56 | + | "work_item.milestoned", | |
| 57 | + | "work_item.demilestoned", | |
| 58 | + | "work_item.closed", | |
| 59 | + | "work_item.reopened", | |
| 60 | + | "work_item.linked", | |
| 61 | + | "work_item.unlinked", | |
| 62 | + | "work_item.landed", | |
| 60 | 63 | "comment.created", | |
| 61 | 64 | "comment.edited", | |
| 62 | 65 | "comment.deleted", | |
| 1 | − | //! The work service: issues, pull requests, comments and sessions. | |
| 1 | + | //! The work service: work items, pull requests, comments and sessions. | |
| 2 | 2 | //! | |
| 3 | 3 | //! Each `*Args` struct is the argument of the method of the same name, | |
| 4 | 4 | //! served at `POST /rpc/<method>`. | |
| 5 | 5 | //! | |
| 6 | − | //! Issues and pull requests share one sequence of numbers per repository, | |
| 7 | − | //! so `#12` names exactly one of them. | |
| 6 | + | //! Work items belong to a workspace and link the repositories the work | |
| 7 | + | //! touches. Work items and new pull requests share one sequence of numbers | |
| 8 | + | //! per workspace, so `#12` in a workspace names exactly one of them; pull | |
| 9 | + | //! requests still live in their repositories. See [`refs`] for how `#N` | |
| 10 | + | //! in text resolves. | |
| 8 | 11 | ||
| 9 | 12 | use serde::{Deserialize, Serialize}; | |
| 10 | 13 | ||
| ⋯ | |||
| 19 | 22 | mod coordination; | |
| 20 | 23 | pub use coordination::*; | |
| 21 | 24 | ||
| 25 | + | mod items; | |
| 26 | + | pub use items::*; | |
| 27 | + | ||
| 28 | + | pub mod refs; | |
| 29 | + | ||
| 22 | 30 | /// The labels a new repository starts with, and that "Add the default | |
| 23 | 31 | /// labels" adds to one that is missing some: `(name, color, description)`. | |
| 24 | 32 | /// Colors are six hex digits, without `#`. | |
| 25 | 33 | pub const DEFAULT_LABELS: [(&str, &str, &str); 11] = [ | |
| 26 | 34 | ("bug", "d73a4a", "Something isn't working"), | |
| 27 | 35 | ("documentation", "0075ca", "Improvements or additions to documentation"), | |
| 28 | − | ("duplicate", "cfd3d7", "This issue or pull request already exists"), | |
| 36 | + | ("duplicate", "cfd3d7", "This work item or pull request already exists"), | |
| 29 | 37 | ("enhancement", "a2eeef", "New feature or request"), | |
| 30 | 38 | ("good first issue", "7057ff", "Good for newcomers"), | |
| 31 | 39 | ("help wanted", "008672", "Extra attention is needed"), | |
| ⋯ | |||
| 63 | 71 | } | |
| 64 | 72 | } | |
| 65 | 73 | ||
| 66 | − | /// The most labels one issue or pull request can carry, and the longest | |
| 67 | − | /// name and description of one. | |
| 74 | + | /// The most labels one work item or pull request can carry, and the | |
| 75 | + | /// longest name and description of one. | |
| 68 | 76 | pub const MAX_LABELS: usize = 20; | |
| 69 | 77 | pub const MAX_LABEL_CHARS: usize = 50; | |
| 70 | 78 | pub const MAX_LABEL_DESCRIPTION_CHARS: usize = 100; | |
| 71 | 79 | ||
| 72 | − | /// A label of a repository: a name, a color and what it means. Issues and | |
| 73 | − | /// pull requests carry labels by name. Names are lowercase and unique in | |
| 74 | − | /// a repository. | |
| 80 | + | /// A label of a workspace: a name, a color and what it means. Work items | |
| 81 | + | /// and the pull requests of its repositories carry labels by name. Names | |
| 82 | + | /// are lowercase and unique in a workspace. | |
| 75 | 83 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 76 | 84 | #[serde(rename_all = "camelCase")] | |
| 77 | 85 | pub struct Label { | |
| ⋯ | |||
| 80 | 88 | pub color: String, | |
| 81 | 89 | #[serde(default)] | |
| 82 | 90 | pub description: String, | |
| 83 | − | /// How many issues carry it, open or closed. | |
| 91 | + | /// How many work items carry it, open or closed. | |
| 84 | 92 | #[serde(default)] | |
| 85 | − | pub issues: u32, | |
| 93 | + | pub items: u32, | |
| 86 | 94 | /// How many pull requests carry it, in any state. | |
| 87 | 95 | #[serde(default)] | |
| 88 | 96 | pub pulls: u32, | |
| 89 | 97 | } | |
| 90 | 98 | ||
| 91 | − | /// A milestone, as an issue or pull request names it. | |
| 99 | + | /// A milestone, as a work item or pull request names it. | |
| 92 | 100 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 93 | 101 | #[serde(rename_all = "camelCase")] | |
| 94 | 102 | pub struct MilestoneRef { | |
| ⋯ | |||
| 96 | 104 | pub title: String, | |
| 97 | 105 | } | |
| 98 | 106 | ||
| 99 | − | /// A milestone: a goal, with an optional due date, that issues and pull | |
| 100 | − | /// requests are gathered under. Its progress is how many of them are | |
| 101 | − | /// closed (a merged pull request counts as closed). | |
| 107 | + | /// A milestone of a workspace: a goal, with an optional due date, that | |
| 108 | + | /// work items and pull requests are gathered under. Its progress is how | |
| 109 | + | /// many of them are closed (a merged pull request counts as closed). | |
| 102 | 110 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 103 | 111 | #[serde(rename_all = "camelCase")] | |
| 104 | 112 | pub struct Milestone { | |
| 105 | − | /// Numbered from 1 in each repository, apart from issues. | |
| 113 | + | /// Numbered from 1 in each workspace, apart from work items. | |
| 106 | 114 | pub number: u32, | |
| 107 | 115 | pub title: String, | |
| 108 | 116 | /// Markdown. | |
| ⋯ | |||
| 112 | 120 | #[serde(default)] | |
| 113 | 121 | pub due_on: Option<String>, | |
| 114 | 122 | pub state: State, | |
| 115 | − | /// Open issues and pull requests in it (drafts count as open). | |
| 123 | + | /// Open work items and pull requests in it (drafts count as open). | |
| 116 | 124 | #[serde(default)] | |
| 117 | 125 | pub open_items: u32, | |
| 118 | − | /// Closed issues, and merged or closed pull requests, in it. | |
| 126 | + | /// Closed work items, and merged or closed pull requests, in it. | |
| 119 | 127 | #[serde(default)] | |
| 120 | 128 | pub closed_items: u32, | |
| 129 | + | /// For a milestone moved from a repository when work items moved to | |
| 130 | + | /// the workspace: that repository's id. | |
| 131 | + | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 132 | + | pub origin_repo_id: Option<String>, | |
| 121 | 133 | /// RFC 3339. | |
| 122 | 134 | pub created_at: String, | |
| 123 | 135 | /// RFC 3339. | |
| ⋯ | |||
| 152 | 164 | ((1..=12).contains(&month) && (1..=31).contains(&date)).then(|| day.to_owned()) | |
| 153 | 165 | } | |
| 154 | 166 | ||
| 155 | − | /// `list_labels`: a repository's labels, by name, each with how many | |
| 156 | − | /// issues and pull requests carry it. Takes `ViewArgs`; returns | |
| 167 | + | /// `list_labels`: a workspace's labels, by name, each with how many work | |
| 168 | + | /// items and pull requests carry it. Takes [`WorkspaceViewArgs`]; returns | |
| 157 | 169 | /// `Outcome<Vec<Label>>`. | |
| 158 | 170 | /// | |
| 159 | 171 | /// `save_label`: creates a label, or with `name` changes one (renaming it | |
| 160 | − | /// renames it on every issue and pull request). Needs the Triage role. | |
| 161 | − | /// Returns `Outcome<Label>`. | |
| 172 | + | /// renames it on every work item and pull request). Needs the Write role | |
| 173 | + | /// in the workspace (`Capability::ManageLabels`). Returns `Outcome<Label>`. | |
| 162 | 174 | #[derive(Debug, Serialize, Deserialize)] | |
| 163 | 175 | #[serde(rename_all = "camelCase")] | |
| 164 | 176 | pub struct SaveLabelArgs { | |
| 165 | 177 | pub actor: User, | |
| 166 | − | pub repo: RepoPath, | |
| 178 | + | /// The workspace's slug. | |
| 179 | + | pub workspace: String, | |
| 167 | 180 | /// The label to change; absent to create one. | |
| 168 | 181 | #[serde(default)] | |
| 169 | 182 | pub name: Option<String>, | |
| ⋯ | |||
| 177 | 190 | pub description: Option<String>, | |
| 178 | 191 | } | |
| 179 | 192 | ||
| 180 | − | /// `delete_label`: removes a label from the repository and from every | |
| 181 | − | /// issue and pull request that carries it. Needs the Triage role. Returns | |
| 182 | − | /// `Outcome<bool>`. | |
| 193 | + | /// `delete_label`: removes a label from the workspace and from every work | |
| 194 | + | /// item and pull request that carries it. Needs `ManageLabels` in the | |
| 195 | + | /// workspace. Returns `Outcome<bool>`. | |
| 183 | 196 | #[derive(Debug, Serialize, Deserialize)] | |
| 184 | 197 | pub struct DeleteLabelArgs { | |
| 185 | 198 | pub actor: User, | |
| 186 | − | pub repo: RepoPath, | |
| 199 | + | pub workspace: String, | |
| 187 | 200 | pub name: String, | |
| 188 | 201 | } | |
| 189 | 202 | ||
| 190 | − | /// `add_default_labels`: adds those of [`DEFAULT_LABELS`] the repository | |
| 191 | − | /// does not have yet. Needs the Triage role. Returns | |
| 203 | + | /// `add_default_labels`: adds those of [`DEFAULT_LABELS`] the workspace | |
| 204 | + | /// does not have yet. Needs `ManageLabels` in the workspace. Returns | |
| 192 | 205 | /// `Outcome<Vec<Label>>`, every label it has now. | |
| 193 | 206 | #[derive(Debug, Serialize, Deserialize)] | |
| 194 | − | pub struct RepoActorArgs { | |
| 207 | + | pub struct WorkspaceActorArgs { | |
| 195 | 208 | pub actor: User, | |
| 196 | − | pub repo: RepoPath, | |
| 209 | + | pub workspace: String, | |
| 210 | + | } | |
| 211 | + | ||
| 212 | + | /// `list_labels`: what a workspace keeps, for whoever may see one of its | |
| 213 | + | /// work items or is a member. | |
| 214 | + | #[derive(Debug, Serialize, Deserialize)] | |
| 215 | + | pub struct WorkspaceViewArgs { | |
| 216 | + | pub workspace: String, | |
| 217 | + | pub viewer: Viewer, | |
| 197 | 218 | } | |
| 198 | 219 | ||
| 199 | − | /// How `set_labels` changes an issue's or pull request's labels. | |
| 220 | + | /// How `set_labels` changes a work item's or pull request's labels. | |
| 200 | 221 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 201 | 222 | #[serde(rename_all = "lowercase")] | |
| 202 | 223 | pub enum LabelChange { | |
| ⋯ | |||
| 209 | 230 | Remove, | |
| 210 | 231 | } | |
| 211 | 232 | ||
| 212 | − | /// `set_labels`: changes the labels of an issue or a pull request (they | |
| 213 | − | /// share numbers). A label the repository does not have yet is created | |
| 214 | − | /// when the actor has the Triage role; anyone else may only use existing | |
| 215 | − | /// ones, on what they opened. Returns `Outcome<Vec<String>>`, its labels | |
| 216 | − | /// now. | |
| 233 | + | /// `set_labels`: changes the labels of a thread (see [`AddCommentArgs`]): | |
| 234 | + | /// with `repo`, its pull request `number`; without, the work item `number` | |
| 235 | + | /// of `workspace`. A label the workspace does not have yet is created when | |
| 236 | + | /// the actor may manage labels; anyone else may only use existing ones, | |
| 237 | + | /// and only on what they opened unless they may triage. Returns | |
| 238 | + | /// `Outcome<Vec<String>>`, its labels now. | |
| 217 | 239 | #[derive(Debug, Serialize, Deserialize)] | |
| 218 | 240 | #[serde(rename_all = "camelCase")] | |
| 219 | 241 | pub struct SetLabelsArgs { | |
| 220 | 242 | pub actor: User, | |
| 221 | − | pub repo: RepoPath, | |
| 243 | + | #[serde(default)] | |
| 244 | + | pub repo: Option<RepoPath>, | |
| 245 | + | #[serde(default)] | |
| 246 | + | pub workspace: String, | |
| 222 | 247 | pub number: u32, | |
| 223 | 248 | pub labels: Vec<String>, | |
| 224 | 249 | #[serde(default)] | |
| 225 | 250 | pub change: LabelChange, | |
| 226 | 251 | } | |
| 227 | 252 | ||
| 228 | − | /// `list_milestones`: a repository's milestones, open ones by due date | |
| 253 | + | /// `list_milestones`: a workspace's milestones, open ones by due date | |
| 229 | 254 | /// (soonest first, those without one last), then closed ones most recently | |
| 230 | 255 | /// closed first. Returns `Outcome<Vec<Milestone>>`. | |
| 231 | 256 | #[derive(Debug, Serialize, Deserialize)] | |
| 232 | 257 | pub struct ListMilestonesArgs { | |
| 233 | − | pub repo: RepoPath, | |
| 258 | + | pub workspace: String, | |
| 234 | 259 | pub viewer: Viewer, | |
| 235 | 260 | /// Both when absent. | |
| 236 | 261 | #[serde(default)] | |
| 237 | 262 | pub state: Option<State>, | |
| 238 | 263 | } | |
| 239 | 264 | ||
| 240 | − | /// `get_milestone`: one milestone and what is in it. Takes `ViewArgs`, | |
| 241 | − | /// whose `number` is the milestone's. Returns `Outcome<MilestoneDetail>`. | |
| 265 | + | /// `get_milestone`: one milestone and what is in it. Takes | |
| 266 | + | /// [`WorkItemViewArgs`], whose `number` is the milestone's. Returns | |
| 267 | + | /// `Outcome<MilestoneDetail>`. | |
| 242 | 268 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 243 | 269 | pub struct MilestoneDetail { | |
| 244 | 270 | pub milestone: Milestone, | |
| 245 | − | /// Newest first, open and closed. | |
| 246 | − | pub issues: Vec<Issue>, | |
| 271 | + | /// Newest first, open and closed, those the viewer may see. | |
| 272 | + | pub items: Vec<WorkItem>, | |
| 247 | 273 | /// Newest first, in any state. | |
| 248 | 274 | pub pulls: Vec<Pull>, | |
| 249 | 275 | } | |
| 250 | 276 | ||
| 251 | − | /// `save_milestone`: creates a milestone, or with `number` changes the | |
| 252 | − | /// fields given. Needs the Triage role. Returns `Outcome<Milestone>`. | |
| 277 | + | /// `save_milestone`: creates a milestone of a workspace, or with `number` | |
| 278 | + | /// changes the fields given. Needs `ManageLabels` in the workspace. | |
| 279 | + | /// Returns `Outcome<Milestone>`. | |
| 253 | 280 | #[derive(Debug, Serialize, Deserialize)] | |
| 254 | 281 | #[serde(rename_all = "camelCase")] | |
| 255 | 282 | pub struct SaveMilestoneArgs { | |
| 256 | 283 | pub actor: User, | |
| 257 | − | pub repo: RepoPath, | |
| 284 | + | pub workspace: String, | |
| 258 | 285 | /// The milestone to change; absent to create one. | |
| 259 | 286 | #[serde(default)] | |
| 260 | 287 | pub number: Option<u32>, | |
| ⋯ | |||
| 271 | 298 | } | |
| 272 | 299 | ||
| 273 | 300 | /// `delete_milestone`: removes a milestone; what was in it is in none. | |
| 274 | − | /// Needs the Triage role. Returns `Outcome<bool>`. | |
| 301 | + | /// Needs `ManageLabels` in the workspace. Returns `Outcome<bool>`. | |
| 275 | 302 | #[derive(Debug, Serialize, Deserialize)] | |
| 276 | 303 | pub struct DeleteMilestoneArgs { | |
| 277 | 304 | pub actor: User, | |
| 278 | − | pub repo: RepoPath, | |
| 305 | + | pub workspace: String, | |
| 279 | 306 | pub number: u32, | |
| 280 | 307 | } | |
| 281 | 308 | ||
| 282 | − | /// `open` or `closed`: the filter on lists of issues and pull requests. An | |
| 309 | + | /// `open` or `closed`: the filter on lists of work items and pull requests. An | |
| 283 | 310 | /// open pull request is a draft or one ready for review; a closed one was | |
| 284 | 311 | /// merged or closed without merging. | |
| 285 | 312 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| ⋯ | |||
| 289 | 316 | Closed, | |
| 290 | 317 | } | |
| 291 | 318 | ||
| 292 | − | /// Why an issue was closed. | |
| 293 | 319 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 294 | − | #[serde(rename_all = "snake_case")] | |
| 295 | − | pub enum IssueReason { | |
| 296 | − | /// The work was done. If a pull request did it, `resolved_by` names it. | |
| 297 | − | Completed, | |
| 298 | − | NotPlanned, | |
| 299 | − | } | |
| 300 | − | ||
| 301 | − | impl IssueReason { | |
| 302 | − | pub fn as_str(self) -> &'static str { | |
| 303 | − | match self { | |
| 304 | − | IssueReason::Completed => "completed", | |
| 305 | − | IssueReason::NotPlanned => "not_planned", | |
| 306 | − | } | |
| 307 | − | } | |
| 308 | − | } | |
| 309 | − | ||
| 310 | − | /// Something that should change in a repository: a bug, a feature, a | |
| 311 | − | /// question. Opened by a person, an agent or an integration. Pull requests | |
| 312 | − | /// are made against it; the one that is merged resolves it. | |
| 313 | − | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 314 | − | #[serde(rename_all = "camelCase")] | |
| 315 | − | pub struct Issue { | |
| 316 | − | pub id: String, | |
| 317 | − | pub repo_id: String, | |
| 318 | − | /// Shown as `#12`. | |
| 319 | − | pub number: u32, | |
| 320 | − | pub title: String, | |
| 321 | − | /// Markdown. Also what an agent is given to work from. | |
| 322 | − | pub body: String, | |
| 323 | − | pub labels: Vec<String>, | |
| 324 | − | pub state: State, | |
| 325 | − | /// Set when closed. | |
| 326 | − | pub reason: Option<IssueReason>, | |
| 327 | − | /// The number of the pull request whose merge closed this issue. | |
| 328 | − | pub resolved_by: Option<u32>, | |
| 329 | − | /// The commit pushed to the default branch whose message closed this | |
| 330 | − | /// issue (`Closes #12`), when a commit rather than a pull request did. | |
| 331 | − | #[serde(default)] | |
| 332 | − | pub resolved_commit: Option<String>, | |
| 333 | − | /// Who pushed that commit, by username. | |
| 334 | − | #[serde(default)] | |
| 335 | − | pub resolved_pushed_by: Option<String>, | |
| 336 | − | /// Who opened it: a person, an integration, or g1t (`kind` `agent`) | |
| 337 | − | /// for one its agent filed while at work. | |
| 338 | − | pub author: User, | |
| 339 | − | /// For an issue g1t's agent filed: the person it was working for. They | |
| 340 | − | /// may manage it as its author could. See [`owner`](Issue::owner). | |
| 341 | − | #[serde(default)] | |
| 342 | − | pub requested_by: Option<User>, | |
| 343 | − | /// RFC 3339. | |
| 344 | − | pub created_at: String, | |
| 345 | − | /// RFC 3339. | |
| 346 | − | pub updated_at: String, | |
| 347 | − | /// RFC 3339. | |
| 348 | − | pub closed_at: Option<String>, | |
| 349 | − | /// Pull requests made against this issue, in any state. | |
| 350 | − | pub pull_count: u32, | |
| 351 | − | pub comment_count: u32, | |
| 352 | − | /// Usernames of the people it is assigned to. | |
| 353 | − | #[serde(default)] | |
| 354 | − | pub assignees: Vec<String>, | |
| 355 | − | /// The numbers of the issues that have to be merged before this one is | |
| 356 | − | /// worked on. | |
| 357 | − | #[serde(default)] | |
| 358 | − | pub blocked_by: Vec<u32>, | |
| 359 | − | /// The agent working on it now: the one behind its newest pull request | |
| 360 | − | /// that is still in progress in a fork, such as `g1t`. | |
| 361 | − | #[serde(default)] | |
| 362 | − | pub agent: Option<String>, | |
| 363 | − | /// The milestone it is in, if any. | |
| 364 | − | #[serde(default)] | |
| 365 | − | pub milestone: Option<MilestoneRef>, | |
| 366 | − | } | |
| 367 | − | ||
| 368 | − | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 369 | 320 | #[serde(rename_all = "lowercase")] | |
| 370 | 321 | pub enum PullStatus { | |
| 371 | 322 | /// Still being worked on. | |
| ⋯ | |||
| 409 | 360 | pub struct Pull { | |
| 410 | 361 | pub id: String, | |
| 411 | 362 | pub repo_id: String, | |
| 412 | − | /// Shown as `#12`. | |
| 363 | + | /// Shown as `#12`. New ones are numbered in their workspace's | |
| 364 | + | /// sequence, which work items share. | |
| 413 | 365 | pub number: u32, | |
| 414 | − | /// The number of the issue this is for, if any. | |
| 415 | − | pub issue: Option<u32>, | |
| 366 | + | /// The work item this is for, if any, by number in the workspace of | |
| 367 | + | /// its repository. | |
| 368 | + | pub item: Option<u32>, | |
| 416 | 369 | pub title: String, | |
| 417 | 370 | /// Markdown: what changed and why. Set when marked ready. | |
| 418 | 371 | pub body: Option<String>, | |
| ⋯ | |||
| 455 | 408 | /// asked: see [`AutoMerge`]. | |
| 456 | 409 | #[serde(default)] | |
| 457 | 410 | pub auto_merge: Option<AutoMerge>, | |
| 458 | − | /// The issues it closes when it merges into the default branch: `issue` | |
| 459 | − | /// and those its title and description name after a closing keyword | |
| 460 | − | /// (`Fixes #12`, or `closes owner/repo#3` naming this repository), in | |
| 461 | − | /// the order named. Empty where the repository does not close issues. | |
| 411 | + | /// The work items it closes when it merges into the default branch, | |
| 412 | + | /// by number in its workspace: `item` and those its title and | |
| 413 | + | /// description name after a closing keyword (`Fixes #12`, or | |
| 414 | + | /// `closes owner/name#3` for a repository of the same workspace), in | |
| 415 | + | /// the order named. Merging marks this repository's link to each as | |
| 416 | + | /// landed. Empty where the repository does not close work items. | |
| 462 | 417 | #[serde(default)] | |
| 463 | 418 | pub closes: Vec<u32>, | |
| 419 | + | /// The work items it is part of (`Part of #12`, `Refs #12`): it is | |
| 420 | + | /// linked to them, without counting toward closing them. | |
| 421 | + | #[serde(default)] | |
| 422 | + | pub parts: Vec<u32>, | |
| 464 | 423 | /// Its branch was deleted after it merged or closed, and can be | |
| 465 | 424 | /// restored at `head_commit` (`restore_head_branch`). | |
| 466 | 425 | #[serde(default)] | |
| 467 | 426 | pub head_branch_deleted: bool, | |
| 468 | − | /// Set on a pull request closed because another one for the same issue | |
| 469 | − | /// was merged: that one's number. | |
| 427 | + | /// Set on a pull request closed because another one for the same work | |
| 428 | + | /// item was merged: that one's number. | |
| 470 | 429 | pub superseded_by: Option<u32>, | |
| 471 | 430 | /// `failed` when the merge queue took it out because its combined | |
| 472 | 431 | /// state failed, until its head moves. Its checks are the statuses | |
| ⋯ | |||
| 503 | 462 | /// for a change g1t made. | |
| 504 | 463 | pub author: User, | |
| 505 | 464 | /// For a change g1t made: the person who asked for it, by assigning | |
| 506 | − | /// an issue or handing g1t the work. They answer for it as its author | |
| 465 | + | /// a work item or handing g1t the work. They answer for it as its author | |
| 507 | 466 | /// would. See [`owner`](Pull::owner). | |
| 508 | 467 | #[serde(default)] | |
| 509 | 468 | pub requested_by: Option<User>, | |
| ⋯ | |||
| 529 | 488 | } | |
| 530 | 489 | ||
| 531 | 490 | /// Another pull request in progress that changes some of the same files. | |
| 532 | − | /// Two for the same issue are alternatives; two for different issues are | |
| 533 | − | /// heading for a conflict. | |
| 491 | + | /// Two for the same work item are alternatives; two for different ones | |
| 492 | + | /// are heading for a conflict. | |
| 534 | 493 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 535 | 494 | pub struct Overlap { | |
| 536 | 495 | pub number: u32, | |
| 537 | 496 | pub title: String, | |
| 538 | − | /// The number of the issue the other pull request is for. | |
| 539 | − | pub issue: Option<u32>, | |
| 497 | + | /// The number of the work item the other pull request is for. | |
| 498 | + | pub item: Option<u32>, | |
| 540 | 499 | /// The files both change. | |
| 541 | 500 | pub paths: Vec<String>, | |
| 542 | 501 | } | |
| 543 | 502 | ||
| 544 | − | impl Issue { | |
| 545 | − | /// Who the issue is theirs to manage: the person g1t's agent filed it | |
| 546 | − | /// for, or its author. They may edit, close and reopen it without the | |
| 547 | − | /// Triage role. | |
| 548 | − | pub fn owner(&self) -> &User { | |
| 549 | − | self.requested_by.as_ref().unwrap_or(&self.author) | |
| 550 | − | } | |
| 551 | − | } | |
| 552 | 503 | ||
| 553 | 504 | impl Pull { | |
| 554 | 505 | /// Who the pull request is theirs to answer for: whoever asked g1t to | |
| ⋯ | |||
| 656 | 607 | } | |
| 657 | 608 | } | |
| 658 | 609 | ||
| 659 | − | /// Who is recorded as opening an issue or a pull request that `actor` | |
| 610 | + | /// Who is recorded as opening a work item or a pull request that `actor` | |
| 660 | 611 | /// opens, and who asked for it: `(author, requested_by)`. | |
| 661 | 612 | /// | |
| 662 | 613 | /// - A session g1t started for someone, which acts as that person with | |
| ⋯ | |||
| 718 | 669 | } | |
| 719 | 670 | ||
| 720 | 671 | /// How one command went. Recorded by earlier runs of commands written on | |
| 721 | − | /// issues, which g1t no longer runs; kept so their history still reads. | |
| 672 | + | /// work items, which g1t no longer runs; kept so their history still reads. | |
| 722 | 673 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 723 | 674 | #[serde(rename_all = "camelCase")] | |
| 724 | 675 | pub struct CheckResult { | |
| ⋯ | |||
| 736 | 687 | } | |
| 737 | 688 | ||
| 738 | 689 | /// A record against a pull request's head: the merge queue taking it out, | |
| 739 | − | /// with why, or an earlier run of commands written on its issue. | |
| 690 | + | /// with why, or an earlier run of commands written on its work item. | |
| 740 | 691 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 741 | 692 | #[serde(rename_all = "camelCase")] | |
| 742 | 693 | pub struct CheckRun { | |
| ⋯ | |||
| 770 | 721 | } | |
| 771 | 722 | } | |
| 772 | 723 | ||
| 773 | − | /// An emoji reaction to an issue, a pull request or a comment: the same | |
| 724 | + | /// An emoji reaction to a work item, a pull request or a comment: the same | |
| 774 | 725 | /// eight as everywhere else people react to code. | |
| 775 | 726 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] | |
| 776 | 727 | pub enum ReactionContent { | |
| ⋯ | |||
| 852 | 803 | groups | |
| 853 | 804 | } | |
| 854 | 805 | ||
| 855 | − | /// `react`: `actor` reacts with `content` to comment `comment_id` on issue | |
| 856 | − | /// or pull request `number`, or to its description when `comment_id` is | |
| 857 | − | /// unset; with `remove`, takes that reaction back. Anyone who can read the | |
| 858 | − | /// repository may. Returns `Outcome<Vec<ReactionGroup>>`: what it has now. | |
| 806 | + | /// `react`: `actor` reacts with `content` to comment `comment_id` on a | |
| 807 | + | /// thread (see [`AddCommentArgs`]), or to its description when | |
| 808 | + | /// `comment_id` is unset; with `remove`, takes that reaction back. Anyone | |
| 809 | + | /// who can read it may. Returns `Outcome<Vec<ReactionGroup>>`: what it has | |
| 810 | + | /// now. | |
| 859 | 811 | #[derive(Debug, Serialize, Deserialize)] | |
| 860 | 812 | #[serde(rename_all = "camelCase")] | |
| 861 | 813 | pub struct ReactArgs { | |
| 862 | 814 | pub actor: User, | |
| 863 | − | pub repo: RepoPath, | |
| 815 | + | #[serde(default)] | |
| 816 | + | pub repo: Option<RepoPath>, | |
| 817 | + | #[serde(default)] | |
| 818 | + | pub workspace: String, | |
| 864 | 819 | pub number: u32, | |
| 865 | 820 | #[serde(default)] | |
| 866 | 821 | pub comment_id: Option<String>, | |
| ⋯ | |||
| 880 | 835 | Event, | |
| 881 | 836 | } | |
| 882 | 837 | ||
| 883 | − | /// A comment on an issue or a pull request. On a pull request it can sit | |
| 838 | + | /// A comment on a work item or a pull request. On a pull request it can sit | |
| 884 | 839 | /// on one line of the change, and it can carry a reviewer's verdict. | |
| 885 | 840 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 886 | 841 | #[serde(rename_all = "camelCase")] | |
| ⋯ | |||
| 1023 | 978 | pub commit: Option<String>, | |
| 1024 | 979 | } | |
| 1025 | 980 | ||
| 1026 | − | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1027 | − | pub struct IssueDetail { | |
| 1028 | − | pub issue: Issue, | |
| 1029 | − | /// Every pull request made against it, oldest first. | |
| 1030 | − | pub pulls: Vec<Pull>, | |
| 1031 | − | pub comments: Vec<Comment>, | |
| 1032 | − | /// How people (and g1t) reacted to its description. | |
| 1033 | − | #[serde(default)] | |
| 1034 | − | pub reactions: Vec<ReactionGroup>, | |
| 1035 | − | } | |
| 1036 | 981 | ||
| 1037 | 982 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1038 | 983 | pub struct PullDetail { | |
| 1039 | 984 | pub pull: Pull, | |
| 1040 | − | /// The issue it is for, if any. | |
| 1041 | − | pub issue: Option<Issue>, | |
| 985 | + | /// The work item it is for, if any. | |
| 986 | + | pub item: Option<WorkItem>, | |
| 1042 | 987 | pub comments: Vec<Comment>, | |
| 1043 | 988 | /// How people (and g1t) reacted to its description. | |
| 1044 | 989 | #[serde(default)] | |
| 1045 | 990 | pub reactions: Vec<ReactionGroup>, | |
| 1046 | 991 | /// The latest record against its head: the merge queue taking it out, | |
| 1047 | − | /// or (from before checks were workflows) a run of its issue's commands. | |
| 992 | + | /// or (from before checks were workflows) a run of its work item's commands. | |
| 1048 | 993 | pub checks: Option<CheckRun>, | |
| 1049 | 994 | /// Other pull requests in progress that change the same files. | |
| 1050 | 995 | #[serde(default)] | |
| ⋯ | |||
| 1229 | 1174 | pub viewer: Viewer, | |
| 1230 | 1175 | } | |
| 1231 | 1176 | ||
| 1232 | − | /// The heading an issue's plain-words description of done goes under. | |
| 1177 | + | /// The heading a work item's plain-words description of done goes under. | |
| 1233 | 1178 | pub const DEFINITION_OF_DONE: &str = "## Definition of done"; | |
| 1234 | 1179 | ||
| 1235 | − | /// An issue's body with `items` added as a bulleted "Definition of done" | |
| 1180 | + | /// A work item's body with `items` added as a bulleted "Definition of done" | |
| 1236 | 1181 | /// section, for the agent and reviewers to read. Nothing is added when | |
| 1237 | 1182 | /// `items` is empty, or when the body already has the section, so folding | |
| 1238 | 1183 | /// the same items twice changes nothing. | |
| ⋯ | |||
| 1385 | 1330 | pub source: Option<String>, | |
| 1386 | 1331 | } | |
| 1387 | 1332 | ||
| 1388 | − | /// A step on the way from an assigned issue to a pull request that is ready | |
| 1333 | + | /// A step on the way from an assigned work item to a pull request that is ready | |
| 1389 | 1334 | /// to merge. g1t takes each one without being asked. | |
| 1390 | 1335 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1391 | 1336 | #[serde(rename_all = "snake_case")] | |
| ⋯ | |||
| 1475 | 1420 | /// (its CODEOWNERS file) have approved, as many as each section asks. | |
| 1476 | 1421 | /// Only people count, and `g1t` only where the file names `@g1t`. | |
| 1477 | 1422 | pub require_code_owner_review: bool, | |
| 1478 | − | /// Whether issues can be opened in it: null for the default, which is | |
| 1479 | − | /// on for a repository and off for a fork of one. Existing issues stay | |
| 1480 | − | /// readable either way. | |
| 1481 | − | pub has_issues: Option<bool>, | |
| 1482 | − | /// Whether issues can be opened in it now: `has_issues`, or its | |
| 1483 | − | /// default. Filled in by the service; `update_settings` ignores it. | |
| 1484 | − | pub issues_enabled: bool, | |
| 1423 | + | /// Whether work items can be filed from it (with it as their origin): | |
| 1424 | + | /// null for the default, which is on for a repository and off for a | |
| 1425 | + | /// fork of one. Work items filed from it before stay readable either | |
| 1426 | + | /// way, and it can always be linked as a target. | |
| 1427 | + | pub has_work_items: Option<bool>, | |
| 1428 | + | /// Whether work items can be filed from it now: `has_work_items`, or | |
| 1429 | + | /// its default. Filled in by the service; `update_settings` ignores it. | |
| 1430 | + | pub work_items_enabled: bool, | |
| 1485 | 1431 | /// Which merge methods a pull request may use (at least one). Rules | |
| 1486 | 1432 | /// can narrow them for a branch (`allowed_merge_methods`). | |
| 1487 | 1433 | #[serde(default = "yes")] | |
| ⋯ | |||
| 1502 | 1448 | pub squash_commit_message: CommitMessageDefault, | |
| 1503 | 1449 | /// For `Custom`: a template whose `{title}`, `{number}`, `{body}`, | |
| 1504 | 1450 | /// `{branch}`, `{head_repo}`, `{base}`, `{author}`, `{commits}`, | |
| 1505 | − | /// `{issues}` and `{co_authors}` are filled in. | |
| 1451 | + | /// `{items}` and `{co_authors}` are filled in. | |
| 1506 | 1452 | #[serde(default)] | |
| 1507 | 1453 | pub merge_commit_template: Option<String>, | |
| 1508 | 1454 | #[serde(default)] | |
| ⋯ | |||
| 1526 | 1472 | /// comments stay readable either way. | |
| 1527 | 1473 | #[serde(default = "yes")] | |
| 1528 | 1474 | pub allow_commit_comments: bool, | |
| 1529 | − | /// Close the issues a pull request names (its issue, and `Fixes #12` | |
| 1530 | − | /// in its title or description) when it merges into the default | |
| 1531 | − | /// branch. | |
| 1475 | + | /// Mark the work items a pull request names (its item, and `Fixes #12` | |
| 1476 | + | /// in its title or description) landed in this repository when it | |
| 1477 | + | /// merges into the default branch, closing those with nothing else | |
| 1478 | + | /// left to land; and the same for commits pushed there. | |
| 1532 | 1479 | #[serde(default = "yes")] | |
| 1533 | − | pub auto_close_issues: bool, | |
| 1480 | + | pub auto_close_items: bool, | |
| 1534 | 1481 | /// Username of the member who last changed the settings, if anyone has. | |
| 1535 | 1482 | pub updated_by: Option<String>, | |
| 1536 | 1483 | /// RFC 3339. | |
| ⋯ | |||
| 1548 | 1495 | allow_ignoring_checks: true, | |
| 1549 | 1496 | merge_queue: false, | |
| 1550 | 1497 | require_code_owner_review: false, | |
| 1551 | − | has_issues: None, | |
| 1552 | − | issues_enabled: true, | |
| 1498 | + | has_work_items: None, | |
| 1499 | + | work_items_enabled: true, | |
| 1553 | 1500 | allow_merge_commit: true, | |
| 1554 | 1501 | allow_squash_merge: true, | |
| 1555 | 1502 | allow_rebase_merge: true, | |
| ⋯ | |||
| 1564 | 1511 | delete_branch_on_merge: false, | |
| 1565 | 1512 | require_web_commit_sign_off: false, | |
| 1566 | 1513 | allow_commit_comments: true, | |
| 1567 | − | auto_close_issues: true, | |
| 1514 | + | auto_close_items: true, | |
| 1568 | 1515 | updated_by: None, | |
| 1569 | 1516 | updated_at: None, | |
| 1570 | 1517 | } | |
| ⋯ | |||
| 1572 | 1519 | } | |
| 1573 | 1520 | ||
| 1574 | 1521 | impl RepoSettings { | |
| 1575 | − | /// Whether issues can be opened: as `has_issues` says, else on unless | |
| 1576 | − | /// the repository is a fork (`is_fork`). | |
| 1577 | − | pub fn issues_on(&self, is_fork: bool) -> bool { | |
| 1578 | − | self.has_issues.unwrap_or(!is_fork) | |
| 1522 | + | /// Whether work items can be filed from it: as `has_work_items` says, | |
| 1523 | + | /// else on unless the repository is a fork (`is_fork`). | |
| 1524 | + | pub fn work_items_on(&self, is_fork: bool) -> bool { | |
| 1525 | + | self.has_work_items.unwrap_or(!is_fork) | |
| 1579 | 1526 | } | |
| 1580 | 1527 | ||
| 1581 | 1528 | /// Whether the repository lets pull requests merge by `method`. | |
| ⋯ | |||
| 1638 | 1585 | pub page: Option<u32>, | |
| 1639 | 1586 | } | |
| 1640 | 1587 | ||
| 1641 | − | /// `closed_by_commit`: the issues of a repository that a commit pushed to | |
| 1642 | − | /// its default branch closed by naming them in its message, for anyone who | |
| 1643 | − | /// may read the repository. Returns `Outcome<Vec<ClosedIssue>>`. | |
| 1588 | + | /// `closed_by_commit`: the work items a commit pushed to a repository's | |
| 1589 | + | /// default branch resolved by naming them in its message, for anyone who | |
| 1590 | + | /// may read the repository. Returns `Outcome<Vec<ClosedItem>>`. | |
| 1644 | 1591 | #[derive(Debug, Serialize, Deserialize)] | |
| 1645 | 1592 | pub struct ClosedByCommitArgs { | |
| 1646 | 1593 | pub viewer: Viewer, | |
| ⋯ | |||
| 1648 | 1595 | pub commit: String, | |
| 1649 | 1596 | } | |
| 1650 | 1597 | ||
| 1651 | − | /// An issue a commit closed. | |
| 1598 | + | /// A work item a commit resolved: it landed in the commit's repository, | |
| 1599 | + | /// and closed once nothing else was left to land. | |
| 1652 | 1600 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 1653 | − | pub struct ClosedIssue { | |
| 1601 | + | pub struct ClosedItem { | |
| 1602 | + | pub workspace: String, | |
| 1654 | 1603 | pub number: u32, | |
| 1655 | 1604 | pub title: String, | |
| 1605 | + | /// Whether the work item is closed now. | |
| 1606 | + | #[serde(default)] | |
| 1607 | + | pub closed: bool, | |
| 1656 | 1608 | } | |
| 1657 | 1609 | ||
| 1658 | 1610 | /// `add_commit_comment`: needs Read (refused where the repository turned | |
| ⋯ | |||
| 1717 | 1669 | pub struct ManagedPullsArgs { | |
| 1718 | 1670 | #[serde(default)] | |
| 1719 | 1671 | pub repo_id: Option<String>, | |
| 1720 | − | } | |
| 1721 | − | ||
| 1722 | − | /// `open_issue`. Returns `Outcome<Issue>`. | |
| 1723 | − | #[derive(Debug, Serialize, Deserialize)] | |
| 1724 | − | pub struct OpenIssueArgs { | |
| 1725 | − | pub actor: User, | |
| 1726 | − | pub repo: RepoPath, | |
| 1727 | − | pub title: String, | |
| 1728 | − | #[serde(default)] | |
| 1729 | − | pub body: String, | |
| 1730 | − | #[serde(default)] | |
| 1731 | − | pub labels: Vec<String>, | |
| 1732 | − | /// Deprecated: commands, added to the body under "Definition of done". | |
| 1733 | − | /// Checks are the workflows the branch's protection requires. | |
| 1734 | − | #[serde(default)] | |
| 1735 | − | pub checks: Vec<String>, | |
| 1736 | − | /// The number of the milestone to put it in. Needs the Triage role. | |
| 1737 | − | #[serde(default)] | |
| 1738 | − | pub milestone: Option<u32>, | |
| 1739 | 1672 | } | |
| 1740 | 1673 | ||
| 1741 | − | /// `list_issues`, newest first. Returns `Outcome<Vec<Issue>>`. | |
| 1742 | − | #[derive(Debug, Serialize, Deserialize)] | |
| 1743 | − | pub struct ListIssuesArgs { | |
| 1744 | − | pub repo: RepoPath, | |
| 1745 | − | pub viewer: Viewer, | |
| 1746 | − | #[serde(default)] | |
| 1747 | − | pub state: Option<State>, | |
| 1748 | − | /// Only issues carrying this label. | |
| 1749 | − | #[serde(default)] | |
| 1750 | − | pub label: Option<String>, | |
| 1751 | − | /// Only issues in the milestone of this number. | |
| 1752 | − | #[serde(default)] | |
| 1753 | − | pub milestone: Option<u32>, | |
| 1754 | − | } | |
| 1755 | − | ||
| 1756 | 1674 | /// `list_pulls`, newest first. Returns `Outcome<Vec<Pull>>`. | |
| 1757 | 1675 | #[derive(Debug, Serialize, Deserialize)] | |
| 1758 | 1676 | pub struct ListPullsArgs { | |
| ⋯ | |||
| 1799 | 1717 | pub closed: Vec<Pull>, | |
| 1800 | 1718 | } | |
| 1801 | 1719 | ||
| 1802 | − | /// `get_issue` (`Outcome<IssueDetail>`), `get_pull` (`Outcome<PullDetail>`), | |
| 1803 | − | /// `read_session` (`Outcome<Vec<SessionEntry>>`), `list_labels` | |
| 1804 | − | /// (`Outcome<Vec<String>>`) and `counts` (`Outcome<Counts>`). The last two | |
| 1805 | − | /// ignore `number`. | |
| 1720 | + | /// `get_pull` (`Outcome<PullDetail>`), `read_session` | |
| 1721 | + | /// (`Outcome<Vec<SessionEntry>>`) and `counts` (`Outcome<Counts>`, which | |
| 1722 | + | /// ignores `number`). | |
| 1806 | 1723 | #[derive(Debug, Serialize, Deserialize)] | |
| 1807 | 1724 | #[serde(rename_all = "camelCase")] | |
| 1808 | 1725 | pub struct ViewArgs { | |
| ⋯ | |||
| 1815 | 1732 | pub after_seq: u32, | |
| 1816 | 1733 | } | |
| 1817 | 1734 | ||
| 1818 | − | /// How many issues and pull requests are open on a repository. | |
| 1735 | + | /// How many work items linked to a repository, and how many of its pull | |
| 1736 | + | /// requests, are open. | |
| 1819 | 1737 | #[derive(Debug, Serialize, Deserialize)] | |
| 1820 | 1738 | pub struct Counts { | |
| 1821 | − | pub issues: u32, | |
| 1739 | + | pub items: u32, | |
| 1822 | 1740 | pub pulls: u32, | |
| 1823 | 1741 | } | |
| 1824 | 1742 | ||
| 1825 | − | /// `update_issue`: changes whichever fields are given. The author or a | |
| 1826 | − | /// member of the workspace may. Returns `Outcome<Issue>`. | |
| 1743 | + | /// `add_comment`, on a thread: a pull request or a work item. Every method | |
| 1744 | + | /// that takes a thread reads it the same way: with `repo`, the pull | |
| 1745 | + | /// request `number` of that repository; without, the work item `number` | |
| 1746 | + | /// of `workspace`. On a pull request a comment may name a line of the | |
| 1747 | + | /// change, and may carry a verdict; nobody can give a verdict on their own | |
| 1748 | + | /// pull request. Returns `Outcome<Comment>`. | |
| 1827 | 1749 | #[derive(Debug, Serialize, Deserialize)] | |
| 1828 | − | pub struct UpdateIssueArgs { | |
| 1750 | + | pub struct AddCommentArgs { | |
| 1829 | 1751 | pub actor: User, | |
| 1830 | − | pub repo: RepoPath, | |
| 1831 | − | pub number: u32, | |
| 1832 | 1752 | #[serde(default)] | |
| 1833 | − | pub title: Option<String>, | |
| 1834 | − | #[serde(default)] | |
| 1835 | − | pub body: Option<String>, | |
| 1836 | − | #[serde(default)] | |
| 1837 | − | pub labels: Option<Vec<String>>, | |
| 1838 | − | /// Usernames of the people it is assigned to; replaces the whole set. | |
| 1839 | − | #[serde(default)] | |
| 1840 | − | pub assignees: Option<Vec<String>>, | |
| 1841 | − | /// The number of the milestone to put it in; 0 takes it out of its | |
| 1842 | − | /// milestone. Needs the Triage role. | |
| 1753 | + | pub repo: Option<RepoPath>, | |
| 1843 | 1754 | #[serde(default)] | |
| 1844 | − | pub milestone: Option<u32>, | |
| 1845 | − | /// The numbers of the issues of the same repository that have to land | |
| 1846 | − | /// first; replaces the whole set. Refused when one is not an issue, is | |
| 1847 | − | /// the issue itself, or would make a cycle. | |
| 1848 | − | #[serde(default)] | |
| 1849 | − | pub blocked_by: Option<Vec<u32>>, | |
| 1850 | − | } | |
| 1851 | − | ||
| 1852 | − | /// `close_issue` and `reopen_issue`. Each returns `Outcome<Issue>`. | |
| 1853 | − | /// `close_issue` takes [`CloseIssueArgs`], which these are a part of. | |
| 1854 | − | #[derive(Debug, Serialize, Deserialize)] | |
| 1855 | − | pub struct IssueActionArgs { | |
| 1856 | − | pub actor: User, | |
| 1857 | − | pub repo: RepoPath, | |
| 1755 | + | pub workspace: String, | |
| 1858 | 1756 | pub number: u32, | |
| 1859 | − | /// For `close_issue`; `completed` if left out. | |
| 1860 | − | #[serde(default)] | |
| 1861 | − | pub reason: Option<IssueReason>, | |
| 1862 | − | } | |
| 1863 | − | ||
| 1864 | − | /// `close_issue`: an [`IssueActionArgs`], and the commit that did it, if | |
| 1865 | − | /// one did. With `commit`, a commit on the default branch whose message | |
| 1866 | − | /// names the issue after a closing keyword (`Fixes #12`), it closes as the | |
| 1867 | − | /// push of that commit would have: completed, in that commit, by the | |
| 1868 | − | /// caller; a commit that is not on the default branch, or does not name | |
| 1869 | − | /// the issue, is refused. Returns `Outcome<Issue>`. | |
| 1870 | − | #[derive(Debug, Serialize, Deserialize)] | |
| 1871 | − | pub struct CloseIssueArgs { | |
| 1872 | − | #[serde(flatten)] | |
| 1873 | − | pub issue: IssueActionArgs, | |
| 1874 | − | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1875 | − | pub commit: Option<String>, | |
| 1876 | − | } | |
| 1877 | − | ||
| 1878 | − | /// `add_comment`, on an issue or a pull request. On a pull request it may | |
| 1879 | − | /// name a line of the change, and may carry a verdict; nobody can give a | |
| 1880 | − | /// verdict on their own pull request. Returns `Outcome<Comment>`. | |
| 1881 | − | #[derive(Debug, Serialize, Deserialize)] | |
| 1882 | − | pub struct AddCommentArgs { | |
| 1883 | − | pub actor: User, | |
| 1884 | − | pub repo: RepoPath, | |
| 1885 | − | pub number: u32, | |
| 1886 | 1757 | /// May be empty when approving. | |
| 1887 | 1758 | #[serde(default)] | |
| 1888 | 1759 | pub body: String, | |
| ⋯ | |||
| 1894 | 1765 | pub verdict: Option<Verdict>, | |
| 1895 | 1766 | } | |
| 1896 | 1767 | ||
| 1897 | − | /// `edit_comment` and `delete_comment`: change the text of a comment on an | |
| 1898 | − | /// issue or a pull request, or delete it. Its author may, and so may anyone | |
| 1899 | − | /// with the Maintain role or higher ([`may_change_comment`]). Notes of what | |
| 1768 | + | /// `edit_comment` and `delete_comment`: change the text of a comment, or | |
| 1769 | + | /// delete it: one on a pull request of `repo`, or without `repo`, one on a | |
| 1770 | + | /// work item of `workspace`. Its author may, and so may anyone with the | |
| 1771 | + | /// Maintain role or higher ([`may_change_comment`]). Notes of what | |
| 1900 | 1772 | /// happened (`CommentKind::Event`) cannot be changed, and a review that | |
| 1901 | 1773 | /// gave a verdict cannot be deleted, only edited. `edit_comment` returns | |
| 1902 | 1774 | /// `Outcome<Comment>`; `delete_comment` returns `Outcome<()>`. | |
| ⋯ | |||
| 1904 | 1776 | #[serde(rename_all = "camelCase")] | |
| 1905 | 1777 | pub struct CommentActionArgs { | |
| 1906 | 1778 | pub actor: User, | |
| 1907 | − | pub repo: RepoPath, | |
| 1779 | + | #[serde(default)] | |
| 1780 | + | pub repo: Option<RepoPath>, | |
| 1781 | + | #[serde(default)] | |
| 1782 | + | pub workspace: String, | |
| 1908 | 1783 | pub comment_id: String, | |
| 1909 | 1784 | /// For `edit_comment`: the new text, in Markdown. | |
| 1910 | 1785 | #[serde(default)] | |
| ⋯ | |||
| 1943 | 1818 | /// `open_pull`. Without `branch`, forks the repo and returns a draft pull | |
| 1944 | 1819 | /// request to push to. With it, opens a pull request, ready for review, | |
| 1945 | 1820 | /// for a branch already pushed to the repo, or to `head_repo`, a fork of | |
| 1946 | − | /// it. Returns `Outcome<Pull>`. | |
| 1821 | + | /// it. Its number is the next in its workspace's sequence. Opening one for | |
| 1822 | + | /// a work item links the repository to the item (`touched`) unless it is | |
| 1823 | + | /// linked already. Returns `Outcome<Pull>`. | |
| 1947 | 1824 | #[derive(Debug, Serialize, Deserialize)] | |
| 1948 | 1825 | pub struct OpenPullArgs { | |
| 1949 | 1826 | pub actor: User, | |
| 1950 | 1827 | pub repo: RepoPath, | |
| 1951 | − | /// The issue this is for. | |
| 1828 | + | /// The work item this is for, by number in the repository's workspace. | |
| 1952 | 1829 | #[serde(default)] | |
| 1953 | − | pub issue: Option<u32>, | |
| 1954 | − | /// Defaults to the issue's title; required without an issue. | |
| 1830 | + | pub item: Option<u32>, | |
| 1831 | + | /// Defaults to the work item's title; required without one. | |
| 1955 | 1832 | #[serde(default)] | |
| 1956 | 1833 | pub title: String, | |
| 1957 | 1834 | /// What changed and why. Usually set later, when a draft is marked ready. | |
| ⋯ | |||
| 2106 | 1983 | /// For `ready_pull`: what changed and why. | |
| 2107 | 1984 | #[serde(default)] | |
| 2108 | 1985 | pub summary: String, | |
| 2109 | − | /// For `merge_pull`: leave the issue open and the other pull requests | |
| 2110 | − | /// for it untouched, because this one is only part of the work. | |
| 1986 | + | /// For `merge_pull`: leave the work item open and the other pull | |
| 1987 | + | /// requests for it untouched, because this one is only part of the | |
| 1988 | + | /// work: it is recorded as part of it, as `Part of #N` would be. | |
| 2111 | 1989 | #[serde(default)] | |
| 2112 | − | pub keep_issue_open: bool, | |
| 1990 | + | pub keep_item_open: bool, | |
| 2113 | 1991 | /// For `merge_pull`: merge although required checks have not passed, | |
| 2114 | 1992 | /// where the repository lets members bypass them. | |
| 2115 | 1993 | #[serde(default)] | |
| ⋯ | |||
| 2213 | 2091 | #[derive(Debug, Serialize, Deserialize)] | |
| 2214 | 2092 | pub struct ActivePull { | |
| 2215 | 2093 | pub pull: Pull, | |
| 2216 | − | pub issue: Option<Issue>, | |
| 2094 | + | pub item: Option<WorkItem>, | |
| 2217 | 2095 | /// Where it stands, for one g1t is seeing through. | |
| 2218 | 2096 | #[serde(default)] | |
| 2219 | 2097 | pub lifecycle: Option<Lifecycle>, | |
| 2220 | 2098 | } | |
| 2221 | 2099 | ||
| 2222 | 2100 | /// `list_active_pulls`: drafts and open pull requests the viewer started, | |
| 2223 | − | /// most recently active first. Returns `Vec<ActivePull>`. Also | |
| 2224 | − | /// `list_assigned_issues`: open issues assigned to the viewer, most | |
| 2225 | − | /// recently changed first. Returns `Vec<Issue>`. | |
| 2101 | + | /// most recently active first. Returns `Vec<ActivePull>`. | |
| 2226 | 2102 | #[derive(Debug, Serialize, Deserialize)] | |
| 2227 | 2103 | pub struct ViewerArgs { | |
| 2228 | 2104 | pub viewer: Viewer, | |
| ⋯ | |||
| 2511 | 2387 | ||
| 2512 | 2388 | // --- A person's work ------------------------------------------------------- | |
| 2513 | 2389 | ||
| 2514 | − | /// Issues or pull requests, on a person's profile. | |
| 2390 | + | /// Work items or pull requests, on a person's profile. | |
| 2515 | 2391 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 2516 | 2392 | #[serde(rename_all = "lowercase")] | |
| 2517 | 2393 | pub enum AuthoredKind { | |
| 2518 | − | Issue, | |
| 2394 | + | Item, | |
| 2519 | 2395 | Pull, | |
| 2520 | 2396 | } | |
| 2521 | 2397 | ||
| ⋯ | |||
| 2545 | 2421 | /// The most items one `by_author` page holds. | |
| 2546 | 2422 | pub const AUTHORED_PAGE: u32 = 25; | |
| 2547 | 2423 | ||
| 2548 | − | /// `by_author`: the issues and pull requests a person opened, only on | |
| 2549 | − | /// repositories `viewer` may read, so a private title never reaches anyone | |
| 2550 | − | /// who could not open it. Returns `Outcome<Authored>`; not found for an | |
| 2424 | + | /// `by_author`: the work items and pull requests a person opened, only | |
| 2425 | + | /// those `viewer` may read, so a private title never reaches anyone who | |
| 2426 | + | /// could not open it. Returns `Outcome<Authored>`; not found for an | |
| 2551 | 2427 | /// account that does not exist. | |
| 2552 | 2428 | #[derive(Debug, Serialize, Deserialize)] | |
| 2553 | 2429 | pub struct ByAuthorArgs { | |
| ⋯ | |||
| 2557 | 2433 | pub kind: Option<AuthoredKind>, | |
| 2558 | 2434 | #[serde(default)] | |
| 2559 | 2435 | pub state: Option<AuthoredState>, | |
| 2560 | − | /// Only work on this repository: `namespace/name`. | |
| 2436 | + | /// Only work on this repository (`namespace/name`): its pull requests, | |
| 2437 | + | /// and the work items filed from it. | |
| 2561 | 2438 | #[serde(default)] | |
| 2562 | 2439 | pub repo: Option<String>, | |
| 2563 | 2440 | #[serde(default)] | |
| ⋯ | |||
| 2570 | 2447 | pub limit: Option<u32>, | |
| 2571 | 2448 | } | |
| 2572 | 2449 | ||
| 2573 | − | /// One issue or pull request a person opened. | |
| 2450 | + | /// One work item or pull request a person opened. | |
| 2574 | 2451 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 2575 | 2452 | #[serde(rename_all = "camelCase")] | |
| 2576 | 2453 | pub struct AuthoredItem { | |
| 2577 | 2454 | pub kind: AuthoredKind, | |
| 2578 | − | pub repo: RepoPath, | |
| 2455 | + | /// The workspace it is numbered in. | |
| 2456 | + | pub workspace: String, | |
| 2457 | + | /// A pull request's repository, or the repository a work item was | |
| 2458 | + | /// filed from, if it was. | |
| 2459 | + | pub repo: Option<RepoPath>, | |
| 2579 | 2460 | pub number: u32, | |
| 2580 | 2461 | pub title: String, | |
| 2581 | 2462 | /// Open or closed; a merged pull request is closed. | |
| 2582 | 2463 | pub state: State, | |
| 2583 | 2464 | /// A pull request's own status. | |
| 2584 | 2465 | pub status: Option<PullStatus>, | |
| 2585 | − | /// Why an issue was closed. | |
| 2586 | − | pub reason: Option<IssueReason>, | |
| 2466 | + | /// Why a work item was closed. | |
| 2467 | + | pub reason: Option<CloseReason>, | |
| 2587 | 2468 | pub draft: bool, | |
| 2588 | 2469 | pub merged: bool, | |
| 2589 | 2470 | /// RFC 3339. | |
| ⋯ | |||
| 2601 | 2482 | pub pulls_merged: u32, | |
| 2602 | 2483 | pub pulls_open: u32, | |
| 2603 | 2484 | pub pulls: u32, | |
| 2604 | − | pub issues: u32, | |
| 2605 | − | pub issues_open: u32, | |
| 2485 | + | pub items: u32, | |
| 2486 | + | pub items_open: u32, | |
| 2606 | 2487 | } | |
| 2607 | 2488 | ||
| 2608 | 2489 | /// A repository a person has opened work on, with how much. | |
| ⋯ | |||
| 2629 | 2510 | pub const CONTRIBUTION_DAYS: u64 = 365; | |
| 2630 | 2511 | ||
| 2631 | 2512 | /// `contributions`: what a person did each day of the last year (commits | |
| 2632 | − | /// they pushed to a default branch, issues and pull requests opened, | |
| 2513 | + | /// they pushed to a default branch, work items and pull requests opened, | |
| 2633 | 2514 | /// reviews given), only on repositories `viewer` may read, for the calendar on their profile. Returns | |
| 2634 | 2515 | /// `Outcome<Contributions>`; not found for an account that does not exist. | |
| 2635 | 2516 | #[derive(Debug, Serialize, Deserialize)] | |
| ⋯ | |||
| 2714 | 2595 | #[test] | |
| 2715 | 2596 | fn a_pull_request_merges_into_its_base_or_the_default_branch() { | |
| 2716 | 2597 | let mut pull: Pull = serde_json::from_value(serde_json::json!({ | |
| 2717 | − | "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": null, "title": "Fix it", "body": null, | |
| 2598 | + | "id": "pr_1", "repoId": "rep_1", "number": 14, "item": null, "title": "Fix it", "body": null, | |
| 2718 | 2599 | "agent": "agent", "runtime": "external", "status": "open", "fork": null, "forkRepoId": null, | |
| 2719 | 2600 | "branch": "fix", "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null, | |
| 2720 | 2601 | "supersededBy": null, "checkStatus": null, "author": { "id": "x", "username": "x" }, | |
| ⋯ | |||
| 2785 | 2666 | ||
| 2786 | 2667 | fn pull(author: User, requested_by: Option<User>) -> Pull { | |
| 2787 | 2668 | let mut pull: Pull = serde_json::from_value(serde_json::json!({ | |
| 2788 | − | "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": 12, "title": "Fix it", "body": null, | |
| 2669 | + | "id": "pr_1", "repoId": "rep_1", "number": 14, "item": 12, "title": "Fix it", "body": null, | |
| 2789 | 2670 | "agent": "g1t", "runtime": "hosted", "status": "open", "fork": null, "forkRepoId": null, | |
| 2790 | 2671 | "branch": null, "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null, | |
| 2791 | 2672 | "supersededBy": null, "checkStatus": null, | |
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Loading the diff…
Comments
Sign in to comment.