{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import requests\n", "import json\n", "import pandas as pd\n", "\n", "import plotly.express as px\n", "import plotly.graph_objects as go\n", "\n", "import ipywidgets as widgets\n", "from IPython.display import display, HTML\n", "from IPython.display import clear_output" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')\n", "GITHUB_TOKEN = input(\"Enter Github Token\") if GITHUB_TOKEN is None else GITHUB_TOKEN\n", "os.environ['GITHUB_TOKEN'] = GITHUB_TOKEN\n", "if GITHUB_TOKEN is None or GITHUB_TOKEN == '':\n", " raise ValueError(\"Please set your GITHUB_TOKEN\")\n", "\n", "REPO = 'microsoft/vscode-jupyter' # Replace with your repository\n", "\n", "pd.set_option('display.max_rows', 100)\n", "pd.set_option('display.min_rows', 100)\n", "pd.set_option(\"display.max_rows\", 100, \"display.max_columns\", 100)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = 'https://api.github.com/graphql'\n", "headers = {'Authorization': f'bearer {GITHUB_TOKEN}'}\n", "\n", "query = '''\n", "query ($repo_owner: String!, $repo_name: String!, $cursor: String) {\n", " repository(owner: $repo_owner, name: $repo_name) {\n", " issues(first: 100, after: $cursor, states: OPEN) {\n", " pageInfo {\n", " endCursor\n", " hasNextPage\n", " }\n", " nodes {\n", " number\n", " title\n", " createdAt\n", " author {\n", " avatarUrl\n", " login\n", " }\n", " reactions{\n", " totalCount\n", " }\n", " labels(first: 10) {\n", " nodes {\n", " name\n", " }\n", " }\n", " assignees(first: 4) {\n", " nodes {\n", " avatarUrl\n", " login\n", " }\n", " }\n", " comments(last:1) {\n", " nodes {\n", " author {\n", " avatarUrl\n", " login\n", " }\n", " createdAt\n", " lastEditedAt\n", " }\n", " }\n", " }\n", " }\n", " }\n", "}'''\n", "\n", "variables = {\n", " 'repo_owner': REPO.split('/')[0],\n", " 'repo_name': REPO.split('/')[1],\n", " 'cursor': None\n", "}\n", "\n", "all_issues = []\n", "\n", "while True:\n", " response = requests.post(url, headers=headers, json={'query': query, 'variables': variables})\n", " data = response.json()\n", " issues = data['data']['repository']['issues']['nodes']\n", " all_issues.extend(issues)\n", " page_info = data['data']['repository']['issues']['pageInfo']\n", " if not page_info['hasNextPage']:\n", " break\n", " variables['cursor'] = page_info['endCursor']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_issue_type(labels):\n", " if 'bug' in labels:\n", " return 'bug'\n", " elif 'feature-request' in labels:\n", " return 'feature-request'\n", " elif 'debt' in labels:\n", " return 'debt'\n", " else:\n", " return 'other'\n", "\n", "issue_details = []\n", "for issue in all_issues:\n", " try:\n", " author = 'ghost' if issue['author'] is None else issue['author']['login']\n", " authorAvatarUrl = 'https://avatars.githubusercontent.com/u/10137?v=4' if issue['author'] is None else issue['author']['avatarUrl']\n", " issue_details.append({\n", " 'id': f'{issue['number']}',\n", " 'reactions': issue['reactions']['totalCount'],\n", " 'title': issue['title'],\n", " '_': f'',\n", " 'Author': f'{author}',\n", " 'labels': ', '.join([label['name'] for label in issue['labels']['nodes']]),\n", " 'Created': pd.to_datetime(issue['createdAt']).strftime(\"%Y-%m-%d\"),\n", " 'Updated': None if len(issue['comments']['nodes']) == 0 else pd.to_datetime(issue['comments']['nodes'][0]['createdAt']).strftime(\"%Y-%m-%d\"),\n", " 'AssignedTo': ' '.join([f'' for assignee in issue['assignees']['nodes']]),\n", " 'type': get_issue_type([label['name'] for label in issue['labels']['nodes']]),\n", " 'assignees': ', '.join([assignee['login'] for assignee in issue['assignees']['nodes']]),\n", " 'labels_list': list([label['name'] for label in issue['labels']['nodes']])\n", " })\n", " except Exception as e:\n", " print(issue)\n", " raise e\n", " break\n", "\n", "print(f\"Total issues: {len(issue_details)}\")\n", "\n", "df = pd.DataFrame(issue_details)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from itables import init_notebook_mode, show\n", "import itables.options as opt\n", "\n", "\n", "init_notebook_mode(all_interactive=True)\n", "opt.showIndex = False\n", "opt.keys = True\n", "opt.column_filters = \"footer\"\n", "opt.columnDefs = [\n", " {\n", " \"targets\": [10, 11],\n", " \"visible\": False\n", " },\n", " {\"className\": \"dt-left\", \"targets\": \"_all\", \"visible\": True},\n", "]\n", "\n", "\n", "def highlight_cells(val):\n", " color = 'red' if 'bug' in val else 'blue'\n", " color = 'green' if 'feature-request' in val else color\n", " color = 'purple' if 'debt' in val else color\n", " return f'background-color: {color}'\n", "\n", "\n", "def display_issue_df(df):\n", "\n", " df = df.style.map(highlight_cells, subset=['type'])\n", "\n", " show(df,\n", " fixedColumns={\"start\": 1, \"end\": 2},\n", " scrollX=True,\n", " search={\"caseInsensitive\": True},\n", " scrollCollapse=True,\n", " scrollY=\"400px\",\n", " paging=False,\n", " buttons=[\"copyHtml5\", \"csvHtml5\", \"excelHtml5\"],\n", " layout={\"topStart\": \"search\", \"topEnd\": \"buttons\"},\n", " select=True\n", " )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a dropdown widget with unique labels\n", "unique_labels = set(label for labels in df['labels'] for label in labels.split(', '))\n", "unique_assignees = set(assignee for assignees in df['assignees'] for assignee in assignees.split(', '))\n", "assignees = widgets.Dropdown(options=sorted(unique_assignees), description='Assignee:')\n", "assignee_issues = df\n", "\n", "out = widgets.Output()\n", "labels = widgets.SelectMultiple(\n", " options=sorted(list(unique_labels)),\n", " value=sorted(list(unique_labels)),\n", " description='Labels',\n", " disabled=False\n", ")\n", "\n", "def build_labels():\n", " issues = df\n", " lables_list = list(set(sum(issues['labels_list'].tolist(), [])))\n", " labels.options = lables_list\n", " labels.value = lables_list\n", " labels.selected_labels = lables_list\n", "\n", "def update_pie_chart():\n", " assignee = assignees.value\n", " out.clear_output(wait=True)\n", " global assignee_issues\n", " assignee_issues = df[df['assignees'].isin([assignee])]\n", "\n", " assignee_issues = assignee_issues[assignee_issues['labels_list'].apply(lambda x: len(list(set(x) & set(labels.value))) > 0)]\n", " total_rows = assignee_issues.shape[0]\n", " if total_rows == 0:\n", " with out:\n", " print(f\"No issues found for assignee '{assignee}' with labels {labels.value}\")\n", " return\n", "\n", " label_counts = assignee_issues['type'].value_counts()\n", "\n", " fig = px.pie(\n", " values=label_counts.values,\n", " names=label_counts.index,\n", " color=label_counts.index,\n", " title=f'{total_rows} Issues grouped by types for \"{assignee}\"',\n", " color_discrete_map={'bug':'red',\n", " 'feature-request':'green',\n", " 'debt':'purple',\n", " 'other':'blue'}\n", " )\n", "\n", " with out:\n", " display(fig)\n", " display_issue_df(assignee_issues)\n", "\n", "def dropdown_eventhandler(change):\n", " update_pie_chart()\n", "\n", "def labels_eventhandler(change):\n", " update_pie_chart()\n", "\n", "assignees.observe(dropdown_eventhandler, names='value')\n", "\n", "\n", "build_labels()\n", "display(assignees)\n", "display(out)\n", "update_pie_chart()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "issue_types = [\"bug\", \"feature-request\", \"debt\", \"other\"]\n", "issue_type_dropdown = widgets.Dropdown(options=sorted(issue_types), description='Type:')\n", "filtered_df = assignee_issues\n", "\n", "out2 = widgets.Output()\n", "\n", "def update_issues_pie_chart(issue_type):\n", " out2.clear_output(wait=True)\n", " if issue_type == 'other':\n", " filtered_df = assignee_issues[~assignee_issues['type'].str.contains('bug|feature-request|debt')]\n", " else:\n", " filtered_df = assignee_issues[assignee_issues['type'].str.contains(issue_type)]\n", " total_rows = filtered_df.shape[0]\n", " def exclude_types(labels):\n", " if labels is None:\n", " return ''\n", " value = ','.join([label for label in labels.split(', ') if label not in ['bug', 'debt', 'feature-request']])\n", " if value is None or value == '':\n", " return ''\n", " return value\n", "\n", " filtered_df.loc[:, 'labels'] = filtered_df['labels'].apply(lambda x: exclude_types(x))\n", " label_counts = filtered_df['labels'].str.split(',').explode().value_counts()\n", " fig = px.pie(values=label_counts.values, names=label_counts.index, title=f'{total_rows} {issue_type} grouped by labels')\n", " with out2:\n", " display(fig)\n", " display_issue_df(filtered_df)\n", "\n", "\n", "def issue_type_dropdown_eventhandler(change):\n", " update_issues_pie_chart(change.new)\n", "\n", "issue_type_dropdown.observe(issue_type_dropdown_eventhandler, names='value')\n", "\n", "display(issue_type_dropdown)\n", "display(out2)\n", "update_issues_pie_chart(issue_type_dropdown.value)" ] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.4" } }, "nbformat": 4, "nbformat_minor": 2 }