Skip to content
222 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Chat controls, public profiles, shadcn selects, and no Docs tab in a project1//! A person's year, for the contribution calendar on their profile: how
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar2//! many commits they pushed to a default branch (or `gh-pages`), issues and
3//! pull requests they opened (or g1t opened for them, see `requested_by`)
4//! and reviews they gave, day by day (UTC).
Chat controls, public profiles, shadcn selects, and no Docs tab in a project5//!
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar6//! Commits are the repos service's: it credits whoever pushed, as each
7//! push lands (`commit_days`, services/repos/src/push_commits.rs), and
8//! counts only repositories the viewer may read, by the same `readable`.
9//!
Chat controls, public profiles, shadcn selects, and no Docs tab in a project10//! As with `by_author` (authored.rs), only repositories the viewer may read
11//! are counted: the repos service decides which (`readable`) over every
12//! repository the person worked in that year, and every count here is then
13//! confined to that set. A day's number never includes work in a private
14//! repository the viewer could not open.
15
16use g1t_contracts::identity::UsernameArgs;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar17use g1t_contracts::repos::{CommitDaysArgs, ReadableArgs, Repo, MAX_READABLE};
Chat controls, public profiles, shadcn selects, and no Docs tab in a project18use g1t_contracts::time::rfc3339;
19use g1t_contracts::work::*;
20use g1t_contracts::{FailureCode, Outcome, Viewer};
21use g1t_kit::now_ms;
22use serde::Deserialize;
23use worker::Result;
24
25use crate::Work;
26
27/// Every repository the person did something in since `?2`, busiest first.
28const TOUCHED_SQL: &str = "SELECT repo_id, count(*) AS n FROM (
29 SELECT repo_id FROM issues WHERE COALESCE(requested_by_id, author_id) = ?1 AND created_at >= ?2
30 UNION ALL SELECT repo_id FROM pulls WHERE COALESCE(requested_by_id, author_id) = ?1 AND created_at >= ?2
31 UNION ALL SELECT repo_id FROM comments WHERE author_id = ?1 AND created_at >= ?2 AND verdict IS NOT NULL
32 ) GROUP BY repo_id ORDER BY n DESC LIMIT ?3";
33
34/// What they did each day since `?3`, in the repositories `?2` (a JSON
35/// array of ids) only.
36const DAYS_SQL: &str = "SELECT day, count(*) AS n FROM (
37 SELECT substr(created_at, 1, 10) AS day FROM issues
38 WHERE COALESCE(requested_by_id, author_id) = ?1 AND created_at >= ?3
39 AND repo_id IN (SELECT value FROM json_each(?2))
40 UNION ALL
41 SELECT substr(created_at, 1, 10) AS day FROM pulls
42 WHERE COALESCE(requested_by_id, author_id) = ?1 AND created_at >= ?3
43 AND repo_id IN (SELECT value FROM json_each(?2))
44 UNION ALL
45 SELECT substr(created_at, 1, 10) AS day FROM comments
46 WHERE author_id = ?1 AND created_at >= ?3 AND verdict IS NOT NULL
47 AND repo_id IN (SELECT value FROM json_each(?2))
48 ) GROUP BY day ORDER BY day";
49
50#[derive(Deserialize)]
51struct RepoCount {
52 repo_id: String,
53}
54
55#[derive(Deserialize)]
56struct DayRow {
57 day: String,
58 n: u32,
59}
60
61/// The first day counted, `YYYY-MM-DD`, when it is `now_ms`: today and the
62/// days before it, [`CONTRIBUTION_DAYS`] in all.
63fn first_day(now_ms: u64) -> String {
64 let back = (CONTRIBUTION_DAYS - 1) * 86_400_000;
65 rfc3339(now_ms.saturating_sub(back))[..10].to_owned()
66}
67
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar68/// The rows and the days' commits as the calendar's days and their total.
69fn tally(rows: Vec<DayRow>, commits: Vec<ContributionDay>, from: String) -> Contributions {
70 let mut by_day = std::collections::BTreeMap::<String, (u32, u32)>::new();
71 for row in rows {
72 by_day.entry(row.day).or_default().0 += row.n;
73 }
74 for day in commits {
75 by_day.entry(day.date).or_default().1 += day.commits;
76 }
77 let days: Vec<ContributionDay> = by_day
Chat controls, public profiles, shadcn selects, and no Docs tab in a project78 .into_iter()
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar79 .filter(|(day, (work, commits))| work + commits > 0 && day.len() == 10 && day.as_str() >= from.as_str())
80 .map(|(date, (work, commits))| ContributionDay {
81 date,
82 count: work + commits,
83 commits,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project84 })
85 .collect();
86 let total = days.iter().map(|day| day.count).sum();
87 Contributions { days, total, from }
88}
89
90impl Work {
91 pub(crate) async fn contributions(&self, a: ContributionsArgs) -> Result<Outcome<Contributions>> {
92 let person: Viewer = g1t_kit::call(
93 &self.identity,
94 "user_by_username",
95 &UsernameArgs {
96 username: a.username.trim().to_lowercase(),
97 },
98 )
99 .await?;
100 let Some(person) = person else {
101 return Ok(Outcome::fail(FailureCode::NotFound, "There is no such account."));
102 };
103 let from = first_day(now_ms());
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar104 // Commits come from the repos service, asked at once; a calendar
105 // without them is better than none.
106 let (commits, rows) = futures_util::future::join(
107 g1t_kit::call::<_, Vec<ContributionDay>>(
108 &self.repos,
109 "commit_days",
110 &CommitDaysArgs {
111 user_id: person.id.clone(),
112 since: from.clone(),
113 viewer: a.viewer.clone(),
114 },
115 ),
116 self.work_days(&person.id, &from, a.viewer),
117 )
118 .await;
119 let commits = commits.unwrap_or_else(|error| {
120 worker::console_error!("commit_days for {} failed: {error}", person.username);
121 Vec::new()
122 });
123 Ok(Outcome::Ok(tally(rows?, commits, from)))
124 }
Chat controls, public profiles, shadcn selects, and no Docs tab in a project125
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar126 /// Issues, pull requests and reviews a day, in repositories the viewer may read.
127 async fn work_days(&self, person: &str, from: &str, viewer: Viewer) -> Result<Vec<DayRow>> {
Chat controls, public profiles, shadcn selects, and no Docs tab in a project128 let touched = self
129 .db
130 .prepare(TOUCHED_SQL)
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar131 .bind(&[person.into(), from.into(), (MAX_READABLE as u32).into()])?
Chat controls, public profiles, shadcn selects, and no Docs tab in a project132 .all()
133 .await?
134 .results::<RepoCount>()?;
135 if touched.is_empty() {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar136 return Ok(Vec::new());
Chat controls, public profiles, shadcn selects, and no Docs tab in a project137 }
138 let readable: Vec<Repo> = g1t_kit::call(
139 &self.repos,
140 "readable",
141 &ReadableArgs {
142 ids: touched.into_iter().map(|row| row.repo_id).collect(),
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar143 viewer,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project144 },
145 )
146 .await?;
147 if readable.is_empty() {
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar148 return Ok(Vec::new());
Chat controls, public profiles, shadcn selects, and no Docs tab in a project149 }
150 let ids: Vec<&str> = readable.iter().map(|repo| repo.id.as_str()).collect();
151 let visible = serde_json::to_string(&ids)?;
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar152 self.db
Chat controls, public profiles, shadcn selects, and no Docs tab in a project153 .prepare(DAYS_SQL)
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar154 .bind(&[person.into(), visible.as_str().into(), from.into()])?
Chat controls, public profiles, shadcn selects, and no Docs tab in a project155 .all()
156 .await?
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar157 .results::<DayRow>()
Chat controls, public profiles, shadcn selects, and no Docs tab in a project158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn every_count_is_confined_to_the_readable_set() {
167 // One `json_each(?2)` per kind of work: nothing is counted outside it.
168 assert_eq!(DAYS_SQL.matches("json_each(?2)").count(), 3);
169 assert_eq!(DAYS_SQL.matches("created_at >= ?3").count(), 3);
170 // Reviews are comments with a verdict; plain comments are not counted.
171 assert_eq!(DAYS_SQL.matches("verdict IS NOT NULL").count(), 1);
172 assert_eq!(TOUCHED_SQL.matches("created_at >= ?2").count(), 3);
173 }
174
175 #[test]
176 fn the_year_is_today_and_the_364_days_before() {
177 // 2026-10-08T12:00:00Z.
178 let now = 1_791_460_800_000;
179 assert_eq!(&rfc3339(now)[..10], "2026-10-08");
180 assert_eq!(first_day(now), "2025-10-09");
181 assert_eq!(first_day(0), "1970-01-01");
182 }
183
184 #[test]
185 fn tallies_days_and_drops_the_empty_and_the_early() {
186 let rows = vec![
187 DayRow { day: "2025-10-08".into(), n: 4 },
188 DayRow { day: "2025-10-09".into(), n: 2 },
189 DayRow { day: "2026-01-01".into(), n: 0 },
190 DayRow { day: "2026-10-08".into(), n: 3 },
191 ];
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar192 let year = tally(rows, Vec::new(), "2025-10-09".into());
Chat controls, public profiles, shadcn selects, and no Docs tab in a project193 assert_eq!(year.total, 5);
194 assert_eq!(
195 year.days,
196 vec![
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar197 ContributionDay { date: "2025-10-09".into(), count: 2, commits: 0 },
198 ContributionDay { date: "2026-10-08".into(), count: 3, commits: 0 },
Chat controls, public profiles, shadcn selects, and no Docs tab in a project199 ]
200 );
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar201 assert_eq!(tally(Vec::new(), Vec::new(), "2025-10-09".into()).total, 0);
202 }
203
204 #[test]
205 fn commits_join_the_days_they_were_pushed() {
206 let rows = vec![DayRow { day: "2026-10-01".into(), n: 2 }];
207 let commits = vec![
208 ContributionDay { date: "2026-10-01".into(), count: 3, commits: 3 },
209 ContributionDay { date: "2026-10-02".into(), count: 1, commits: 1 },
210 ContributionDay { date: "2025-01-01".into(), count: 9, commits: 9 },
211 ];
212 let year = tally(rows, commits, "2025-10-09".into());
213 assert_eq!(year.total, 6);
214 assert_eq!(
215 year.days,
216 vec![
217 ContributionDay { date: "2026-10-01".into(), count: 5, commits: 3 },
218 ContributionDay { date: "2026-10-02".into(), count: 1, commits: 1 },
219 ]
220 );
Chat controls, public profiles, shadcn selects, and no Docs tab in a project221 }
222}