Skip to content

Commit 018e307

Browse files
Add query params for bot PRs and a date range to GitHub stats
Allow configuring the "Exclude Bot PRs" toggle via the `exclude_bots` query param and add an optional "Until" date (with `until` query param) so metrics can be scoped to a range instead of only a "since" date. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2de8ae7 commit 018e307

1 file changed

Lines changed: 75 additions & 23 deletions

File tree

app/github_stats.py

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def get_git_fame_stats() -> dict:
5757
ttl=60 * 60 * 72,
5858
show_spinner="Fetching PR metrics (this may take a couple of minutes)...",
5959
)
60-
def fetch_pr_metrics(merged_since: date) -> pd.DataFrame:
61-
return fetch_merged_pr_metrics(merged_since=merged_since)
60+
def fetch_pr_metrics(merged_since: date, merged_until: date | None = None) -> pd.DataFrame:
61+
return fetch_merged_pr_metrics(merged_since=merged_since, merged_until=merged_until)
6262

6363

6464
title_row = st.container(horizontal=True, horizontal_alignment="distribute", vertical_alignment="center")
@@ -95,17 +95,64 @@ def fetch_pr_metrics(merged_since: date) -> pd.DataFrame:
9595
else:
9696
default_since = date.fromisoformat("2022-04-01")
9797

98+
# Get until date from query params. Defaults to today, which effectively
99+
# means "no upper bound" so the range behaves like a plain "since" filter.
100+
until_param = st.query_params.get("until", None)
101+
if until_param:
102+
try:
103+
default_until = date.fromisoformat(until_param)
104+
except ValueError:
105+
default_until = today
106+
else:
107+
default_until = today
108+
109+
# Clamp the range so the "Until" date is never before the "Since" date.
110+
default_until = min(max(default_until, default_since), today)
111+
98112
since_input = st.date_input(
99113
"Since",
100114
value=default_since,
101115
max_value=today,
102-
help="Include PRs and issues closed on or after this date.",
116+
help="Include PRs and issues on or after this date.",
117+
)
118+
until_input = st.date_input(
119+
"Until",
120+
value=default_until,
121+
min_value=since_input,
122+
max_value=today,
123+
help="Include PRs and issues on or before this date. Defaults to today.",
124+
)
125+
126+
# Allow configuring the bot PR toggle via the `exclude_bots` query param
127+
# (e.g. `?exclude_bots=true`).
128+
exclude_bots_param = st.query_params.get("exclude_bots", None)
129+
default_exclude_bots = (
130+
str(exclude_bots_param).strip().lower() in {"true", "1", "yes", "on"}
131+
if exclude_bots_param is not None
132+
else False
103133
)
104-
exclude_bot_prs = st.toggle("Exclude Bot PRs")
134+
exclude_bot_prs = st.toggle("Exclude Bot PRs", value=default_exclude_bots)
135+
136+
# Whether an explicit upper bound has been set (i.e. not the default "today").
137+
has_until_bound = until_input < today
138+
139+
# Human-readable description of the selected time range, used in captions.
140+
if has_until_bound:
141+
period_label = f"between {since_input.strftime('%Y/%m/%d')} and {until_input.strftime('%Y/%m/%d')}"
142+
else:
143+
period_label = f"since {since_input.strftime('%Y/%m/%d')}"
144+
145+
# GitHub search query fragment for the selected merged-date range.
146+
merged_query_suffix = f"merged%3A>={since_input.strftime('%Y-%m-%d')}"
147+
if has_until_bound:
148+
merged_query_suffix += f"+merged%3A<={until_input.strftime('%Y-%m-%d')}"
105149

106150

107151
try:
108-
merged_prs_df = fetch_pr_metrics(merged_since=since_input)
152+
merged_prs_df = fetch_pr_metrics(
153+
merged_since=since_input,
154+
merged_until=until_input if has_until_bound else None,
155+
)
109156
except Exception as ex:
110157
# The GitHub GraphQL API can occasionally fail transiently (e.g. non-JSON
111158
# responses, timeouts, or rate limiting). Show a friendly error and let the
@@ -128,7 +175,7 @@ def fetch_pr_metrics(merged_since: date) -> pd.DataFrame:
128175
st.markdown("#### :material/merge: Merged PRs by Authors")
129176

130177
st.caption(
131-
f"GitHub users who have authored the most merged pull requests on `streamlit/streamlit` merged into `develop` since {since_input.strftime('%Y/%m/%d')}. "
178+
f"GitHub users who have authored the most merged pull requests on `streamlit/streamlit` merged into `develop` {period_label}. "
132179
f"Total merged PRs: **{len(merged_prs_df)}.**"
133180
)
134181

@@ -175,7 +222,7 @@ def fetch_pr_metrics(merged_since: date) -> pd.DataFrame:
175222
# Add links
176223
author_stats["Show PRs"] = author_stats["author"].apply(
177224
lambda x: (
178-
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+author%3A{x}+merged%3A>={since_input.strftime('%Y-%m-%d')}"
225+
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+author%3A{x}+{merged_query_suffix}"
179226
)
180227
)
181228
author_stats["author"] = author_stats["author"].apply(lambda x: f"https://github.com/{x}")
@@ -218,7 +265,7 @@ def fetch_pr_metrics(merged_since: date) -> pd.DataFrame:
218265
st.markdown("#### :material/rate_review: Merged PRs by Reviewers")
219266

220267
st.caption(
221-
f"GitHub users who have reviewed the most pull requests on `streamlit/streamlit` merged into `develop` since {since_input.strftime('%Y/%m/%d')}. "
268+
f"GitHub users who have reviewed the most pull requests on `streamlit/streamlit` merged into `develop` {period_label}. "
222269
f"Total merged PRs: **{len(merged_prs_df)} {'(including bot PRs)' if not exclude_bot_prs else ''}.**"
223270
)
224271

@@ -259,7 +306,7 @@ def calculate_percentage(row: dict) -> float:
259306
# Add links
260307
reviewer_counts["Show PRs"] = reviewer_counts["reviewers"].apply(
261308
lambda x: (
262-
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+reviewed-by%3A{x}+merged%3A>={since_input.strftime('%Y-%m-%d')}"
309+
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+reviewed-by%3A{x}+{merged_query_suffix}"
263310
)
264311
)
265312
reviewer_counts["reviewers"] = reviewer_counts["reviewers"].apply(lambda x: f"https://github.com/{x}")
@@ -328,7 +375,7 @@ def calculate_percentage(row: dict) -> float:
328375
community_prs_df = community_prs_df[~community_prs_df["from_bot"]]
329376

330377
st.caption(
331-
f"GitHub users who have reviewed the most pull requests on `streamlit/streamlit` merged into `develop` since {since_input.strftime('%Y/%m/%d')} that were authored by community members. "
378+
f"GitHub users who have reviewed the most pull requests on `streamlit/streamlit` merged into `develop` {period_label} that were authored by community members. "
332379
f"Total merged community PRs: **{len(community_prs_df)}.**"
333380
)
334381

@@ -357,7 +404,7 @@ def calculate_percentage(row: dict) -> float:
357404
# Add links
358405
community_reviewer_counts["Show PRs"] = community_reviewer_counts["reviewers"].apply(
359406
lambda x: (
360-
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+reviewed-by%3A{x}+merged%3A>={since_input.strftime('%Y-%m-%d')}"
407+
f"https://github.com/streamlit/streamlit/pulls?q=is%3Apr+is%3Amerged+reviewed-by%3A{x}+{merged_query_suffix}"
361408
)
362409
)
363410
community_reviewer_counts["reviewers"] = community_reviewer_counts["reviewers"].apply(
@@ -428,8 +475,9 @@ def calculate_percentage(row: dict) -> float:
428475

429476
# Closers who closed issues with the most reactions
430477
closers_df = all_issues_df.copy()
431-
if since_input:
432-
closers_df = closers_df[closers_df["closed_at"].dt.date >= since_input]
478+
closers_df = closers_df[
479+
(closers_df["closed_at"].dt.date >= since_input) & (closers_df["closed_at"].dt.date <= until_input)
480+
]
433481

434482
closers_df["closed_by_login"] = closers_df["closed_by"].apply(
435483
lambda x: x.get("login", "") if isinstance(x, dict) else ""
@@ -494,7 +542,7 @@ def calculate_percentage(row: dict) -> float:
494542

495543
with title_container:
496544
st.caption(
497-
f"GitHub users sorted by total reactions on issues they closed - via pull request or manual closing - since {since_input.strftime('%Y/%m/%d')}. "
545+
f"GitHub users sorted by total reactions on issues they closed - via pull request or manual closing - {period_label}. "
498546
f"Total closed reactions: **{closers_stats['Total reactions'].sum()}**. Total closed issues: **{closers_stats['Issues closed'].sum()}**. "
499547
f"Total closed bugs: **{closers_stats['Bugs closed'].sum()}**. Total closed enhancements: **{closers_stats['Enhancements closed'].sum()}**. "
500548
)
@@ -588,11 +636,12 @@ def calculate_percentage(row: dict) -> float:
588636
authors_df["author"] = authors_df["user"].apply(lambda x: x.get("login", "") if isinstance(x, dict) else "")
589637
authors_df = authors_df[authors_df["author"] != ""]
590638

591-
if since_input:
592-
authors_df = authors_df[authors_df["created_at"].dt.date >= since_input]
639+
authors_df = authors_df[
640+
(authors_df["created_at"].dt.date >= since_input) & (authors_df["created_at"].dt.date <= until_input)
641+
]
593642

594643
st.caption(
595-
f"GitHub users who created the most issues on `streamlit/streamlit` since {since_input.strftime('%Y/%m/%d')}. "
644+
f"GitHub users who created the most issues on `streamlit/streamlit` {period_label}. "
596645
f"Total issues created: **{len(authors_df)}**."
597646
)
598647

@@ -887,9 +936,7 @@ def calculate_percentage(row: dict) -> float:
887936
elif selected_metrics == "Team Productivity Metrics":
888937
# --- PR Metrics ---
889938
st.markdown("##### :material/merge: Pull Request Metrics")
890-
st.caption(
891-
f"Metrics based on merged pull requests on `streamlit/streamlit` merged into `develop` since {since_input.strftime('%Y/%m/%d')}."
892-
)
939+
st.caption(f"Metrics based on merged pull requests on `streamlit/streamlit` merged into `develop` {period_label}.")
893940

894941
if not merged_prs_df.empty:
895942
# Calculate PR metrics
@@ -1337,6 +1384,7 @@ def calculate_percentage(row: dict) -> float:
13371384
closed_reactions_df = reactions_issues_df[
13381385
(reactions_issues_df["closed_at"].notna())
13391386
& (reactions_issues_df["closed_at"].dt.date >= since_input)
1387+
& (reactions_issues_df["closed_at"].dt.date <= until_input)
13401388
].copy()
13411389

13421390
if not closed_reactions_df.empty:
@@ -1474,18 +1522,22 @@ def calculate_percentage(row: dict) -> float:
14741522
all_issues_df["closed_at"] = pd.to_datetime(all_issues_df["closed_at"])
14751523

14761524
# Filter by date for "Created" metrics
1477-
created_in_period = all_issues_df[all_issues_df["created_at"].dt.date >= since_input]
1525+
created_in_period = all_issues_df[
1526+
(all_issues_df["created_at"].dt.date >= since_input) & (all_issues_df["created_at"].dt.date <= until_input)
1527+
]
14781528

14791529
# Filter by date for "Closed" metrics
14801530
closed_in_period = all_issues_df[
1481-
(all_issues_df["closed_at"].notna()) & (all_issues_df["closed_at"].dt.date >= since_input)
1531+
(all_issues_df["closed_at"].notna())
1532+
& (all_issues_df["closed_at"].dt.date >= since_input)
1533+
& (all_issues_df["closed_at"].dt.date <= until_input)
14821534
]
14831535

14841536
total_created = len(created_in_period)
14851537
total_closed = len(closed_in_period)
14861538

14871539
st.caption(
1488-
f"Metrics based on issues from `streamlit/streamlit` since {since_input.strftime('%Y/%m/%d')}. "
1540+
f"Metrics based on issues from `streamlit/streamlit` {period_label}. "
14891541
f"Total issues created: **{total_created}**."
14901542
)
14911543

0 commit comments

Comments
 (0)