@@ -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
6464title_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
107151try :
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+ )
109156except 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:
887936elif 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