-
Notifications
You must be signed in to change notification settings - Fork 81
Add Contributor Conversion & Drop-off Funnel Visualizations #914
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
5fe5396
09a189c
dc9ceba
4b4f0a7
c2a4776
d439507
03ebbda
e7fb9a7
53850f7
90748f1
799498b
277abb2
f49d15e
238f5d2
cd43be5
089000f
c753ca9
5e337e4
7245719
417d063
55ef5d3
2a3470e
52a1381
561dd6f
8bbed1a
c07a223
0a7ff1c
625eed6
85df2a0
27b338b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| from dash import html, dcc, callback | ||
| import dash | ||
| import dash_bootstrap_components as dbc | ||
| from dash.dependencies import Input, Output, State | ||
| from datetime import date, timedelta, datetime | ||
| import pandas as pd | ||
| import logging | ||
| import plotly.express as px | ||
| from dash.exceptions import PreventUpdate | ||
| import time | ||
|
|
||
| import app | ||
| from cache_manager import cache_facade as cf | ||
| from pages.utils.job_utils import nodata_graph | ||
| from queries.contributors_query import contributors_query as cnq | ||
|
|
||
| PAGE = "contributors" | ||
| VIZ_ID = "contributor-radar" | ||
|
|
||
| gc_contributor_radar = dbc.Card( | ||
| [ | ||
| dbc.CardBody( | ||
| [ | ||
| dbc.Row( | ||
| [ | ||
| dbc.Col( | ||
| html.H3( | ||
| "Contributor Activity Radar", | ||
| className="card-title", | ||
| ), | ||
| ), | ||
| dbc.Col( | ||
| dbc.Button( | ||
| "About Graph", | ||
| id=f"popover-target-{PAGE}-{VIZ_ID}", | ||
| color="outline-secondary", | ||
| size="sm", | ||
| className="about-graph-button", | ||
| ), | ||
| width="auto", | ||
| ), | ||
| ], | ||
| align="center", | ||
| justify="between", | ||
| className="mb-3", | ||
| ), | ||
| dbc.Popover( | ||
| [ | ||
| dbc.PopoverHeader("Graph Info:"), | ||
| dbc.PopoverBody( | ||
| "This radar chart shows the number of unique contributors performing different key activities within the selected time range." | ||
| ), | ||
| ], | ||
| id=f"popover-{PAGE}-{VIZ_ID}", | ||
| target=f"popover-target-{PAGE}-{VIZ_ID}", | ||
| placement="top", | ||
| is_open=False, | ||
| ), | ||
| dcc.Loading( | ||
| dcc.Graph(id=f"{PAGE}-{VIZ_ID}", figure=nodata_graph), | ||
| style={"marginBottom": "1rem"}, | ||
| ), | ||
| html.Hr( # Divider between graph and controls | ||
| style={ | ||
| "borderColor": "#909090", | ||
| "margin": "1.5rem -1.5rem", | ||
| "width": "calc(100% + 3rem)", | ||
| } | ||
| ), | ||
| dbc.Form( | ||
| [ | ||
| dbc.Row( | ||
| [ | ||
| dbc.Label( | ||
| "Select Time Range:", | ||
| html_for=f"date-picker-{PAGE}-{VIZ_ID}", | ||
| width="auto", | ||
| ), | ||
| dbc.Col( | ||
| dcc.DatePickerRange( | ||
| id=f"date-picker-{PAGE}-{VIZ_ID}", | ||
| min_date_allowed=date(2015, 1, 1), | ||
| max_date_allowed=date.today(), | ||
| start_date=date.today() - timedelta(days=180), | ||
| end_date=date.today(), | ||
| display_format='YYYY-MM-DD', | ||
| className="dark-date-picker", | ||
| ), | ||
| width=5, | ||
| ), | ||
| ], | ||
| align="center", | ||
| ), | ||
| ] | ||
| ), | ||
| ], | ||
| style={"padding": "1.5rem"}, | ||
| ), | ||
| ], | ||
| className="dark-card", | ||
| ) | ||
|
|
||
| # callback for graph info popover | ||
| @callback( | ||
| Output(f"popover-{PAGE}-{VIZ_ID}", "is_open"), | ||
| [Input(f"popover-target-{PAGE}-{VIZ_ID}", "n_clicks")], | ||
| [State(f"popover-{PAGE}-{VIZ_ID}", "is_open")], | ||
| ) | ||
| def toggle_popover(n, is_open): | ||
| if n: | ||
| return not is_open | ||
| return is_open | ||
|
|
||
|
|
||
| # callback for contributor radar graph | ||
| @callback( | ||
| Output(f"{PAGE}-{VIZ_ID}", "figure"), | ||
| [ | ||
| Input(f"date-picker-{PAGE}-{VIZ_ID}", "start_date"), | ||
| Input(f"date-picker-{PAGE}-{VIZ_ID}", "end_date"), | ||
| Input("repo-choices", "data"), | ||
| Input("bot-switch", "value"), | ||
| ], | ||
| background=True, | ||
| ) | ||
| def generate_radar_chart_from_data(start_date, end_date, repolist, bot_switch): | ||
| """ | ||
| This callback fetches raw contributor actions, applies the date filter in Pandas, | ||
| aggregates the data, and then creates the radar chart. | ||
| """ | ||
| logging.warning(f"--- {VIZ_ID} CALLBACK TRIGGERED ---") | ||
| if not repolist or not start_date or not end_date: | ||
| raise PreventUpdate | ||
|
|
||
| # wait for data to asynchronously download and become available. | ||
| func_name = cnq.__name__ | ||
| not_cached = cf.get_uncached(func_name=func_name, repolist=repolist) | ||
| if not_cached: | ||
| logging.warning(f"{VIZ_ID}: Raw action data for {len(not_cached)} repos not cached. Dispatching worker.") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this different than the other visualizations? |
||
| cnq.apply_async(args=[not_cached]) | ||
| timeout = 180 | ||
| start_time = time.time() | ||
| while time.time() - start_time < timeout: | ||
| if not cf.get_uncached(func_name=func_name, repolist=not_cached): | ||
| break | ||
| time.sleep(2) | ||
|
|
||
| logging.warning(f"{VIZ_ID} - START") | ||
| start = time.perf_counter() | ||
|
|
||
| # GET ALL DATA FROM POSTGRES CACHE | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are bots not filtered out? |
||
| df = cf.retrieve_from_cache(tablename=func_name, repolist=repolist) | ||
|
|
||
| # test if there is data | ||
| if df.empty: | ||
| logging.warning(f"{VIZ_ID} - NO DATA AVAILABLE") | ||
| return nodata_graph() | ||
|
|
||
| # function for all data pre processing and figure creation | ||
| fig = process_data_and_create_figure(df, start_date, end_date, bot_switch) | ||
|
|
||
| logging.warning(f"{VIZ_ID} - END - {time.perf_counter() - start}") | ||
| return fig | ||
|
|
||
|
|
||
| def process_data_and_create_figure(df: pd.DataFrame, start_date, end_date, bot_switch): | ||
| """ | ||
| Process the raw contributor data, filter by date range and bot switch, | ||
| and create the radar chart figure. | ||
| """ | ||
| # Convert to datetime and filter by date range | ||
| df['created_at'] = pd.to_datetime(df['created_at'], utc=True) | ||
| start_date_dt = pd.to_datetime(start_date, utc=True) | ||
| end_date_dt = pd.to_datetime(end_date, utc=True) | ||
| df = df[(df['created_at'] >= start_date_dt) & (df['created_at'] <= end_date_dt)] | ||
|
|
||
| # Aggregate actions for each contributor | ||
| df_agg = df.groupby(['repo_id', 'cntrb_id', 'login']).agg( | ||
| created_issue=('action', lambda x: 1 if 'issue_opened' in x.values else 0), | ||
| opened_pr=('action', lambda x: 1 if 'pull_request_open' in x.values else 0), | ||
| pr_commented=('action', lambda x: 1 if 'pull_request_comment' in x.values else 0), | ||
| committed=('action', lambda x: 1 if 'commit' in x.values else 0), | ||
| pr_merged=('action', lambda x: 1 if 'pull_request_merged' in x.values else 0), | ||
| ).reset_index() | ||
|
|
||
| # remove bot data if switch is on | ||
| if bot_switch and "cntrb_id" in df_agg.columns: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move into prior function, this applies through line 194 |
||
| df_agg = df_agg[~df_agg["cntrb_id"].isin(app.bots_list)] | ||
|
|
||
| # return no data graph if df is empty after processing | ||
| if df_agg.empty: | ||
| logging.warning(f"{VIZ_ID} - NO DATA AVAILABLE AFTER PROCESSING") | ||
| return nodata_graph() | ||
|
|
||
| # Create the radar chart figure | ||
| activity_metrics = { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be in a create_figure function. See other visualizations or the template |
||
| "Issue Creators": df_agg[df_agg["created_issue"] == 1]["login"].nunique(), | ||
| "PR Openers": df_agg[df_agg["opened_pr"] == 1]["login"].nunique(), | ||
| "PR Commenters": df_agg[df_agg["pr_commented"] == 1]["login"].nunique(), | ||
| "PR Mergers": df_agg[df_agg["pr_merged"] == 1]["login"].nunique(), | ||
| } | ||
|
|
||
| radar_df = pd.DataFrame(dict(Count=list(activity_metrics.values()), Activity=list(activity_metrics.keys()))) | ||
|
|
||
| fig = px.line_polar( | ||
| radar_df, r='Count', theta='Activity', line_close=True, markers=True, | ||
| title=" " | ||
| ) | ||
| fig.update_traces(fill='toself') | ||
| fig.update_layout( | ||
| margin=dict(l=60, r=60, t=60, b=40), | ||
| font=dict(size=14) | ||
| ) | ||
|
|
||
| return fig | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| from .visualizations.contributors_types_over_time import gc_contributors_over_time | ||
| from .visualizations.active_drifting_contributors import gc_active_drifting_contributors | ||
| from .visualizations.new_contributor import gc_new_contributor | ||
| from .visualizations.contributor_funnel import gc_contributor_funnel, gc_contributor_dropoff | ||
|
|
||
|
|
||
| warnings.filterwarnings("ignore") | ||
|
|
||
|
|
@@ -39,6 +41,14 @@ | |
| align="center", | ||
| style={"marginBottom": ".5%"}, | ||
| ), | ||
| dbc.Row( | ||
| [ | ||
| dbc.Col(gc_contributor_funnel, width=6), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move to chaoss page |
||
| dbc.Col(gc_contributor_dropoff, width=6), | ||
| ], | ||
| align="center", | ||
| style={"marginBottom": ".5%"}, | ||
| ), | ||
| ], | ||
| fluid=True, | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please change this to be formatted the same way the other visualizations are done