diff --git a/notebook/dashboard.ipynb b/notebook/dashboard.ipynb index 88a20e95..d948d93b 100644 --- a/notebook/dashboard.ipynb +++ b/notebook/dashboard.ipynb @@ -85,6 +85,8 @@ "from dotenv import load_dotenv\n", "from github import Github, UnknownObjectException\n", "import pandas as pd\n", + "from IPython.display import display\n", + "from ipywidgets import widgets, interactive\n", "import plotly.express as px\n", "import plotly.graph_objects as go\n", "import plotly.io as pio\n", @@ -367,46 +369,180 @@ "# Open PRs\n", "pr_data = []\n", "for repo in df_repos.to_dict('records'):\n", - " draft_prs = 0\n", - " non_draft_prs = 0\n", - " dependabot_prs = 0\n", - "\n", " for pr in repo['prs']:\n", " pr_details = repo['_repo'].get_pull(pr.number)\n", - " if pr_details.user.login == 'dependabot[bot]' or pr_details.user.login == 'renovate[bot]':\n", - " dependabot_prs += 1\n", - " elif pr_details.draft:\n", - " draft_prs += 1\n", - " else:\n", - " non_draft_prs += 1\n", - "\n", - " pr_data.append({\n", - " \"repo\": repo['repo'],\n", - " \"Draft\": draft_prs,\n", - " \"Ready for review\": non_draft_prs,\n", - " \"Dependency\": dependabot_prs,\n", - " })\n", + " pr_data.append({\n", + " \"repo\": repo['repo'],\n", + " \"number\": pr.number,\n", + " \"title\": pr_details.title,\n", + " \"author\": pr_details.user.login,\n", + " \"assignees\": [assignee.login for assignee in pr_details.assignees],\n", + " \"labels\": [label.name for label in pr_details.labels],\n", + " \"ready\": not pr_details.draft,\n", + " \"created_at\": pr_details.created_at if hasattr(pr_details, 'created_at') else None,\n", + " \"updated_at\": pr_details.updated_at if hasattr(pr_details, 'updated_at') else None,\n", + " })\n", "\n", "df_prs = pd.DataFrame(pr_data)\n", - "df_prs['total_prs'] = df_prs[['Draft', 'Ready for review', 'Dependency']].sum(axis=1)\n", "\n", - "# Sort by total PRs in descending order\n", - "df_prs = df_prs.sort_values(by='total_prs', ascending=False)\n", "\n", - "# Visualize data using a stacked bar chart\n", - "fig = px.bar(\n", - " df_prs,\n", - " x='repo',\n", - " y=['Draft', 'Ready for review', 'Dependency'],\n", - " title='Open Pull Requests',\n", - " labels={'value': 'Count', 'variable': 'PR Type'},\n", - " barmode='stack'\n", + "# Helper functions for filtering\n", + "def filter_by_column(df, column, value, exclude=False):\n", + " if value == 'All':\n", + " return df\n", + " if exclude:\n", + " return df[df[column] != value]\n", + " return df[df[column] == value]\n", + "\n", + "def filter_by_list_column(df, column, value, exclude=False):\n", + " if value == 'All':\n", + " return df\n", + " if exclude:\n", + " return df[~df[column].apply(lambda x: value in x)]\n", + " return df[df[column].apply(lambda x: value in x)]\n", + "\n", + "\n", + "# Filter and sort functions\n", + "def filter_prs(\n", + " df,\n", + " repo='All',\n", + " author='All',\n", + " assignee='All',\n", + " label='All',\n", + " ready='All',\n", + " exclude_repo=False,\n", + " exclude_author=False,\n", + " exclude_assignee=False,\n", + " exclude_label=False,\n", + "):\n", + " df = filter_by_column(df, 'repo', repo, exclude_repo)\n", + " df = filter_by_column(df, 'author', author, exclude_author)\n", + " df = filter_by_list_column(df, 'assignees', assignee, exclude_assignee)\n", + " df = filter_by_list_column(df, 'labels', label, exclude_label)\n", + " if ready != 'All':\n", + " df = df[df['ready'] == (ready == 'Ready for Review')]\n", + " return df\n", + "\n", + "# Create dropdown filters\n", + "repo_filter = widgets.Dropdown(\n", + " options=['All'] + df_prs['repo'].unique().tolist(),\n", + " value='All',\n", + " description='Repo:',\n", ")\n", - "fig.update_layout(\n", - " yaxis_title='Count of PRs',\n", - " xaxis_title='Repository',\n", + "\n", + "author_filter = widgets.Dropdown(\n", + " options=['All'] + df_prs['author'].unique().tolist(),\n", + " value='All',\n", + " description='Author:',\n", ")\n", - "fig.show()" + "\n", + "assignee_filter = widgets.Dropdown(\n", + " options=['All'] + list(set(assignee for assignees in df_prs['assignees'] for assignee in assignees)),\n", + " value='All',\n", + " description='Assignee:',\n", + ")\n", + "\n", + "label_filter = widgets.Dropdown(\n", + " options=['All'] + list(set(label for labels in df_prs['labels'] for label in labels)),\n", + " value='All',\n", + " description='Label:',\n", + ")\n", + "\n", + "ready_filter = widgets.Dropdown(\n", + " options=['All', 'Draft', 'Ready for Review'],\n", + " value='All',\n", + " description='Ready:',\n", + ")\n", + "\n", + "# Create exclusion checkboxes\n", + "exclude_repo_checkbox = widgets.Checkbox(\n", + " value=False,\n", + " description='Exclude selected repo',\n", + ")\n", + "\n", + "exclude_author_checkbox = widgets.Checkbox(\n", + " value=False,\n", + " description='Exclude selected author',\n", + ")\n", + "\n", + "exclude_assignee_checkbox = widgets.Checkbox(\n", + " value=False,\n", + " description='Exclude selected assignee',\n", + ")\n", + "\n", + "exclude_label_checkbox = widgets.Checkbox(\n", + " value=False,\n", + " description='Exclude selected label',\n", + ")\n", + "\n", + "# Text widget to display total number of PRs\n", + "total_prs_text = widgets.HTML(value=\"\")\n", + "\n", + "def update_visualizations(\n", + " repo,\n", + " author,\n", + " assignee,\n", + " label,\n", + " ready,\n", + " exclude_repo,\n", + " exclude_author,\n", + " exclude_assignee,\n", + " exclude_label,\n", + "):\n", + " filtered_df = filter_prs(\n", + " df_prs,\n", + " repo,\n", + " author,\n", + " assignee,\n", + " label,\n", + " ready,\n", + " exclude_repo,\n", + " exclude_author,\n", + " exclude_assignee,\n", + " exclude_label,\n", + " )\n", + "\n", + " # Update Table\n", + " fig_table = go.Figure(data=[go.Table(\n", + " header=dict(values=list(filtered_df.columns),\n", + " align='left'),\n", + " cells=dict(values=[filtered_df[col] for col in filtered_df.columns],\n", + " align='left'))\n", + " ])\n", + " fig_table.update_layout(height=800, title='Filtered PRs')\n", + "\n", + " # Update Bar Chart\n", + " df_bar = filtered_df.groupby(['repo', 'ready']).size().reset_index(name='count')\n", + " fig_bar = px.bar(\n", + " df_bar,\n", + " x='repo',\n", + " y='count',\n", + " color='ready',\n", + " title='PRs by Repo and Ready Status',\n", + " labels={'count': 'Number of PRs', 'repo': 'Repository', 'ready': 'Ready Status'},\n", + " barmode='stack',\n", + " text='count'\n", + " )\n", + "\n", + " # Update total PRs text\n", + " total_prs_text.value = f\"Filtered PRs: {len(filtered_df)}\"\n", + "\n", + " fig_table.show()\n", + " fig_bar.show()\n", + "\n", + "interactive_visualizations = interactive(\n", + " update_visualizations,\n", + " repo=repo_filter,\n", + " author=author_filter,\n", + " assignee=assignee_filter,\n", + " label=label_filter,\n", + " ready=ready_filter,\n", + " exclude_repo=exclude_repo_checkbox,\n", + " exclude_author=exclude_author_checkbox,\n", + " exclude_assignee=exclude_assignee_checkbox,\n", + " exclude_label=exclude_label_checkbox,\n", + ")\n", + "display(total_prs_text, interactive_visualizations)" ] }, { diff --git a/requirements.txt b/requirements.txt index 744dbbca..7fdee19d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +ipython==8.26.0 +ipywidgets==8.1.3 notebook==7.2.1 pandas==2.2.2 plotly==5.23.0