diff --git a/src/app.py b/src/app.py index 9df477c..c4e9ec0 100644 --- a/src/app.py +++ b/src/app.py @@ -134,12 +134,164 @@ ) # --------------------------------------------------------- -# 🏆 TAB 2: LEADERBOARD +# 🏆 TAB 2: LEADERBOARD (Wall of Fame) # --------------------------------------------------------- with tab_lead: st.header("Wall of Fame") + st.markdown( + "Publicly acknowledging the most active developers " + "within the ScanAPI ecosystem." + ) + if not df_pulls.empty: - st.dataframe(df_pulls, use_container_width=True) + # --- Interactive Controls Structure --- + col_f1, col_f2, col_f3 = st.columns(3) + + with col_f1: + metric_choice = st.selectbox( + "Engagement Metric:", + options=["Merged Pull Requests", "Active Contributions"], + key="lead_metric", + ) + with col_f2: + time_filter = st.selectbox( + "Leaderboard Timeframe:", + options=["All Time", "Last 30 Days"], + key="lead_time", + ) + with col_f3: + st.markdown( + "
", + unsafe_allow_html=True, + ) + hide_bots = st.checkbox( + "Hide Bot Accounts", value=True, key="lead_hide_bots" + ) + + # Clone dataframe safely + df_lead = df_pulls.copy() + + # --- Exact Field Fallback Assignments --- + # Ensures fields exist even if something went wrong during parsing + if "author" not in df_lead.columns: + df_lead["author"] = df_lead.get("assignee", "Unknown Contributor") + if "avatar_url" not in df_lead.columns: + df_lead["avatar_url"] = "https://github.com" + + # Force conversion to clean string representations + df_lead["author"] = ( + df_lead["author"].fillna("Unknown Contributor").astype(str) + ) + + # --- Interactive Bot Exclusions Filter Logic --- + if hide_bots: + df_lead = df_lead[ + ~df_lead["author"].str.contains( + r"\[bot\]", case=False, na=False + ) + ] + bot_list = [ + "dependabot", + "github-actions", + "greenkeeper", + "snyk-bot", + ] + df_lead = df_lead[~df_lead["author"].str.lower().isin(bot_list)] + + # --- Timeframe Filter Logic (With Timezone Safety) --- + if time_filter == "Last 30 Days" and "created_at" in df_lead.columns: + df_lead["created_at"] = pd.to_datetime( + df_lead["created_at"], errors="coerce" + ) + df_lead = df_lead.dropna(subset=["created_at"]) + + if df_lead["created_at"].dt.tz is not None: + df_lead["created_at"] = df_lead["created_at"].dt.tz_localize( + None + ) + + cutoff = datetime.utcnow() - timedelta(days=30) + df_lead = df_lead[df_lead["created_at"] >= cutoff] + + # Group and rank metrics by true PR Author + if not df_lead.empty: + leaderboard_df = ( + df_lead.groupby(["author", "avatar_url"]) + .size() + .reset_index(name="Volume") + .sort_values(by="Volume", ascending=False) + .reset_index(drop=True) + ) + else: + leaderboard_df = pd.DataFrame() + + # --- Dynamic Dashboard Visual Matrix Layout --- + if not leaderboard_df.empty: + st.markdown("### 🥇 Top Tier Contributors") + # --- Dynamic Caption Evaluation --- + if time_filter == "Last 30 Days": + caption_text = ( + "💡 Metrics reflect ecosystem contributions " + "over the last 30 days." + ) + else: + caption_text = ( + "💡 Metrics reflect ecosystem contributions over a " + "rolling 100-day window." + ) + + st.caption(caption_text) + # --- Dynamic Dashboard Visual Matrix Layout --- + + grid_cols = st.columns(4) + for index, row in leaderboard_df.iterrows(): + col_idx = index % 4 + + # Render clean avatar fallback string configurations + avatar = ( + row["avatar_url"] + if pd.notnull(row["avatar_url"]) + else "https://github.com" + ) + + with grid_cols[col_idx]: + with st.container(border=True): + st.markdown( + f""" +
+ +

+ #{index + 1} {row['author']} +

+
+ """, + unsafe_allow_html=True, + ) + st.metric( + label=metric_choice, value=int(row["Volume"]) + ) + else: + st.info( + "✨ No active contributions match your selected " + "metrics timeframe filters." + ) + else: + st.warning( + "⚠️ Baseline Pull Requests data telemetry is missing or empty." + ) + # --------------------------------------------------------- # 📈 TAB 3: TREND ANALYSIS & OPERATIONAL BOTTLENECKS diff --git a/src/github_client.py b/src/github_client.py index 62c5747..bd0aae1 100644 --- a/src/github_client.py +++ b/src/github_client.py @@ -39,6 +39,13 @@ def fetch_raw_repo_data(repo_name: str): "closed_at": issue.closed_at, "labels": [label.name for label in issue.labels], "assignee": (issue.assignee.login if issue.assignee else None), + # Add these two new keys to track the real author details: + "author": (issue.user.login if issue.user else "Unknown"), + "avatar_url": ( + issue.user.avatar_url + if issue.user + else "https://github.com" + ), } if issue.pull_request: