-
Notifications
You must be signed in to change notification settings - Fork 445
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c96f9d1
commit 9ceadbb
Showing
1 changed file
with
135 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ | |
"tags": [] | ||
}, | ||
"source": [ | ||
"# GitHub - Send templates maintainer monthly report\n", | ||
"# GitHub - Send template maintainer monthly report\n", | ||
"<a href=\"https://app.naas.ai/user-redirect/naas/downloader?url=https://raw.githubusercontent.com/jupyter-naas/awesome-notebooks/master/GitHub/GitHub_Send_templates_created_on_a_notebooks_to_Slack_channel.ipynb\" target=\"_parent\"><img src=\"https://naasai-public.s3.eu-west-3.amazonaws.com/Open_in_Naas_Lab.svg\"/></a><br><br><a href=\"https://bit.ly/3JyWIk6\">Give Feedbacks</a> | <a href=\"https://app.naas.ai/user-redirect/naas/downloader?url=https://raw.githubusercontent.com/jupyter-naas/awesome-notebooks/master/Naas/Naas_Start_data_product.ipynb\" target=\"_parent\">Generate Data Product</a>" | ||
] | ||
}, | ||
|
@@ -31,7 +31,7 @@ | |
"tags": [] | ||
}, | ||
"source": [ | ||
"**Tags:** #github #issues #merged #rest #api #snippet #operations #email" | ||
"**Tags:** #github #issues #merged #rest #api #snippet #operations #email #awesomenotebooks #maintainer" | ||
] | ||
}, | ||
{ | ||
|
@@ -64,12 +64,7 @@ | |
"tags": [] | ||
}, | ||
"source": [ | ||
"**Description:** This notebook demonstrates how to send the issues merged on GitHub this month to an e-mail. It includes the sections below:\n", | ||
"\n", | ||
"- 🔃 **Templates merged this month:** a table and the issues merged this month.\n", | ||
"- 📈 **The estimate of this month:** the number of estimate you got this month \n", | ||
"\n", | ||
"*NB: Execution time may takes between 1 to 3 min.*" | ||
"**Description:** This notebook retrieves data to ascertain the sponsorships provided by Naas for template maintainers and dispatches a notification on every 7th day of the month, as well as the last three days. It incorporates the monthly count of issue closed with estimates, and the number of Pull Requests reviewed within the month." | ||
] | ||
}, | ||
{ | ||
|
@@ -86,8 +81,7 @@ | |
"- [json](https://docs.python.org/3/library/json.html)\n", | ||
"- [datetime](https://docs.python.org/3/library/datetime.html)\n", | ||
"- [pandas](https://pandas.pydata.org/docs/)\n", | ||
"- [requests](https://docs.python-requests.org/en/latest/)\n", | ||
"- [slack (naas_drivers)](https://pypi.org/project/naas-drivers/)" | ||
"- [requests](https://docs.python-requests.org/en/latest/)" | ||
] | ||
}, | ||
{ | ||
|
@@ -130,7 +124,7 @@ | |
"import numpy as np\n", | ||
"from datetime import datetime\n", | ||
"import naas\n", | ||
"import naas_drivers\n", | ||
"from naas_drivers import naasauth, emailbuilder\n", | ||
"import warnings\n", | ||
"warnings.filterwarnings(\"ignore\")" | ||
] | ||
|
@@ -143,15 +137,20 @@ | |
"tags": [] | ||
}, | ||
"source": [ | ||
"### Setup Variables\n", | ||
"### Setup variables\n", | ||
"**Mandatory**\n", | ||
"\n", | ||
"- `github_token`: [GitHub token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line)\n", | ||
"- `repo_name`: name of the repository in two part: owner_name/repository_name\n", | ||
"- `contributor_profile`: GitHub username of the contributor\n", | ||
"- `url`:Url of the project where the estimated are located\n", | ||
"\n", | ||
"**Optional**\n", | ||
"\n", | ||
"- `repo_name`: name of the repository in two part: owner_name/repository_name\n", | ||
"- `estimates_view_url`:Url of the project where the estimated are located\n", | ||
"- `cron`: cron params for naas scheduler change it using [Crontab](https://crontab.guru/)\n", | ||
"- `email_to`: List to email addresses of the receiver(s)\n", | ||
"- `email_to`: This variable is used for storing a list of email addresses that will receive the notification email\n", | ||
"- `email_from`: Email sender: Replace with your email account or [email protected]\n", | ||
"- `subject`: Email subject\n" | ||
"- `subject`: Email subject" | ||
] | ||
}, | ||
{ | ||
|
@@ -164,15 +163,16 @@ | |
}, | ||
"outputs": [], | ||
"source": [ | ||
"#Input\n", | ||
"# Mandatory\n", | ||
"github_token = naas.secret.get(\"GITHUB_TOKEN\") or \"YOUR_GITHUB_TOKEN\"\n", | ||
"repo_name = \"jupyter-naas/awesome-notebooks\" #Example: jupyter-naas/awesome-notebooks\n", | ||
"contributor_profile = \"USERNAME\" \n", | ||
"url = \"https://github.com/orgs/jupyter-naas/projects/10/views/20\" #example: https://github.com/orgs/jupyter-naas/projects/10/views/20\n", | ||
"cron = \"0 0 28-31 * *\" # At 00:00 on every day-of-month from 28 through 31\n", | ||
"contributor_profile = \"FlorentLvr\"\n", | ||
"\n", | ||
"#Output\n", | ||
"email_to = [\"[email protected]\"]\n", | ||
"# Optional\n", | ||
"scenario = \"This Month\" #\"Last month\"\n", | ||
"repo_name = \"jupyter-naas/awesome-notebooks\" #Example: jupyter-naas/awesome-notebooks\n", | ||
"estimates_view_url = \"https://github.com/orgs/jupyter-naas/projects/10/views/20\" #example: https://github.com/orgs/jupyter-naas/projects/10/views/20\n", | ||
"cron = \"0 0 7,14,21,28,30,31 * *\" # At 00:00 on every day-of-month from 28 through 31\n", | ||
"email_to = [naasauth.connect().user.me().get(\"username\")] # List to emails address of the receiver(s)\n", | ||
"email_from = \"[email protected]\"\n", | ||
"subject = \"Templates Maintainer Monthly Report\"" | ||
] | ||
|
@@ -218,87 +218,96 @@ | |
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a804dbf8-edb6-4445-822f-908011f22b1b", | ||
"metadata": { | ||
"papermill": {}, | ||
"tags": [] | ||
}, | ||
"id": "24803d7e-669a-461f-9baa-27cffaf3797f", | ||
"metadata": {}, | ||
"source": [ | ||
"### Get PRs merged\n", | ||
"So here we're filtering out all merged PRs on the `contributor_profile`" | ||
"### Get PRs merged on scenario" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "0db97969-9d57-4e8e-b8bd-058541d36831", | ||
"id": "4b0b3db0-acdb-46cc-9248-e3eb8cfa0cc2", | ||
"metadata": { | ||
"papermill": {}, | ||
"tags": [] | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"# Function to check if a date is within the current month\n", | ||
"def is_date_in_current_month(date_obj):\n", | ||
"# Create scenario to filter PRs\n", | ||
"def get_scenario(scenario):\n", | ||
" # Init\n", | ||
" current_date = datetime.now(timezone.utc)\n", | ||
" return date_obj.year == current_date.year and date_obj.month == current_date.month\n", | ||
" date_scenario = current_date\n", | ||
" \n", | ||
" # Get date from scenario\n", | ||
" if scenario == \"Last Month\":\n", | ||
" month_scenario = current_date.month - 1 \n", | ||
" date_scenario = current_date.replace(month=month_scenario)\n", | ||
" return date_scenario\n", | ||
"\n", | ||
"# Get the merged PRs\n", | ||
"pull_requests = repo.get_pulls(state='closed', sort='updated', direction='desc')\n", | ||
"print(\"✅ Pull Requests fetched:\", pull_requests.totalCount)\n", | ||
"scenario_filter = get_scenario(scenario)\n", | ||
"\n", | ||
"# Filter merged pull requests during this month and assigned to the specific contributor\n", | ||
"merged_assigned_pull_requests = [pr for pr in pull_requests if pr.assignee and pr.assignee.login == contributor_profile and pr.merged_at and is_date_in_current_month(pr.merged_at)]\n", | ||
"# Get PRs closed\n", | ||
"pull_requests = repo.get_pulls(state='closed', sort='updated', direction='desc')\n", | ||
"print(\"✅ Pull Requests closed:\", pull_requests.totalCount)\n", | ||
"\n", | ||
"# Print the merged PR count for the specific contributor during this month\n", | ||
"print(f\"📌Number of assigned merged PRs in current month: {len(merged_assigned_pull_requests)}\")" | ||
"# Get PRs merged on scenario\n", | ||
"merged_pr = []\n", | ||
"for pr in pull_requests:\n", | ||
" # Get PRs merged\n", | ||
" if pr.merged_at:\n", | ||
" merged_at = pr.merged_at.strftime(\"%Y%m\")\n", | ||
" \n", | ||
" # Get PRs merged on scenario\n", | ||
" if int(merged_at) == int(scenario_filter.strftime(\"%Y%m\")):\n", | ||
" merged_pr.append(pr)\n", | ||
" if int(merged_at) < int(scenario_filter.strftime(\"%Y%m\")):\n", | ||
" break\n", | ||
" \n", | ||
"print(f\"📌 Pull Requests merged on {scenario_filter.strftime('%Y-%m')}:\", len(merged_pr))" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "748b932f-68b3-4823-8ba3-8baef3ba98ad", | ||
"metadata": { | ||
"papermill": {}, | ||
"tags": [] | ||
}, | ||
"id": "fe2043a3-0093-4105-8017-ca527f28e624", | ||
"metadata": {}, | ||
"source": [ | ||
"### Get templates added by PRs closed" | ||
"### Get PRs assigned and reviewed by contributor" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "dd9730f4-e5ea-4142-8a80-73a8a84724ae", | ||
"id": "e973208f-b299-4e07-a5e4-dbde4c2ef38f", | ||
"metadata": { | ||
"tags": [] | ||
}, | ||
"outputs": [], | ||
"source": [ | ||
"# Store the relevant information in a DataFrame\n", | ||
"data = []\n", | ||
"for pr in merged_assigned_pull_requests:\n", | ||
" tmp = {\n", | ||
" \"title\": pr.title,\n", | ||
" \"number\": pr.number,\n", | ||
" \"url\": pr.html_url,\n", | ||
" \"assignee\": pr.assignee.login,\n", | ||
" \"created_at\": pr.created_at.isoformat(),\n", | ||
" \"updated_at\": pr.updated_at.isoformat(),\n", | ||
" \"merged_at\": pr.merged_at.isoformat(),\n", | ||
" }\n", | ||
" data.append(tmp)\n", | ||
"print(\"Number of merged PR:\", len(merged_assigned_pull_requests))\n", | ||
"df_pr = pd.DataFrame(data)\n", | ||
"df_pr.head(3)" | ||
"assigned_pr = []\n", | ||
"reviewed_pr = []\n", | ||
"\n", | ||
"for pr in merged_pr:\n", | ||
" # Get PRs assigned\n", | ||
" if pr.assignee and pr.assignee.login == contributor_profile:\n", | ||
" assigned_pr.append(pr)\n", | ||
" \n", | ||
" # Get PRs reviewed\n", | ||
" if pr.requested_reviewers:\n", | ||
" for r in pr.requested_reviewers:\n", | ||
" if r.login == contributor_profile:\n", | ||
" reviewed_pr.append(pr)\n", | ||
"\n", | ||
"print(f\"🧑💻 Pull Requests assigned:\", len(assigned_pr))\n", | ||
"print(f\"👀 Pull Requests reviewed:\", len(reviewed_pr))" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "b97ec4af-a6b2-41e8-a29e-c65fffd9d037", | ||
"metadata": {}, | ||
"source": [ | ||
"### Get Data from project view\n", | ||
"This function returns organised data from the project view soup using BeautifulSoup." | ||
"### Get estimates from project view" | ||
] | ||
}, | ||
{ | ||
|
@@ -314,7 +323,7 @@ | |
"data_bs4 = []\n", | ||
"\n", | ||
"# Get HTML from URL\n", | ||
"response = requests.get(url)\n", | ||
"response = requests.get(estimates_view_url)\n", | ||
"html = response.text\n", | ||
"\n", | ||
"# Parse HTML\n", | ||
|
@@ -353,20 +362,18 @@ | |
" # Append the dictionary to the data list\n", | ||
" data_bs4.append(tmp_bs4)\n", | ||
"\n", | ||
" \n", | ||
"print(\"Number of issues:\", len(split_text))\n", | ||
"# Create a DataFrame from the data list\n", | ||
"df_init = pd.DataFrame(data_bs4)\n", | ||
"df_init.head(3)" | ||
"df_estimates = pd.DataFrame(data_bs4) \n", | ||
"print(\"✅ Row fetched on estimates view:\", len(df_estimates))\n", | ||
"# df_estimates.head(1)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "f14411e4-400a-4eeb-974c-d209794b3b39", | ||
"metadata": {}, | ||
"source": [ | ||
"### Calculate estimates\n", | ||
"I've filtered the issues using the PR URLs" | ||
"### Calculate estimates on issue closed" | ||
] | ||
}, | ||
{ | ||
|
@@ -378,23 +385,42 @@ | |
}, | ||
"outputs": [], | ||
"source": [ | ||
"# Filter df_init with the URLs from df_pr\n", | ||
"filtered_df_init = df_init[df_init[\"PR URL\"].isin(df_pr[\"url\"])]\n", | ||
"filtered_df_init['Estimate'] = filtered_df_init['Estimate'].str.replace(\"null\", \"0\")\n", | ||
"# Store the relevant information in a DataFrame\n", | ||
"data = []\n", | ||
"total_estimate = 0\n", | ||
"filtered_df_init = pd.DataFrame()\n", | ||
"\n", | ||
"for pr in assigned_pr:\n", | ||
" tmp = {\n", | ||
" \"title\": pr.title,\n", | ||
" \"number\": pr.number,\n", | ||
" \"url\": pr.html_url,\n", | ||
" \"assignee\": pr.assignee.login,\n", | ||
" \"created_at\": pr.created_at.isoformat(),\n", | ||
" \"updated_at\": pr.updated_at.isoformat(),\n", | ||
" \"merged_at\": pr.merged_at.isoformat(),\n", | ||
" }\n", | ||
" data.append(tmp)\n", | ||
"df_pr = pd.DataFrame(data)\n", | ||
"\n", | ||
"# Set the display option for max column width to ensure the link is fully displayed\n", | ||
"pd.set_option('display.max_colwidth', None)\n", | ||
"if len(df_pr) > 0:\n", | ||
" # Filter df_init with the URLs from df_pr\n", | ||
" filtered_df_init = df_estimates[df_estimates[\"PR URL\"].isin(df_pr[\"url\"])]\n", | ||
" filtered_df_init['Estimate'] = filtered_df_init['Estimate'].str.replace(\"null\", \"0\")\n", | ||
"\n", | ||
"# Convert the \"PR URL\" column values into clickable links using HTML formatting\n", | ||
"filtered_df_init[\"PR URL\"] = filtered_df_init[\"PR URL\"].apply(lambda x: f'<a href=\"{x}\">{x}</a>')\n", | ||
" # Set the display option for max column width to ensure the link is fully displayed\n", | ||
" pd.set_option('display.max_colwidth', None)\n", | ||
"\n", | ||
"# Calculate the total of the \"Estimate\" column and convert it to an integer\n", | ||
"total_estimate = int(filtered_df_init[\"Estimate\"].astype(float).sum())\n", | ||
" # Convert the \"PR URL\" column values into clickable links using HTML formatting\n", | ||
" filtered_df_init[\"PR URL\"] = filtered_df_init[\"PR URL\"].apply(lambda x: f'<a href=\"{x}\">{x}</a>')\n", | ||
"\n", | ||
"# Display the DataFrame with clickable links and format the \"Estimate\" column as integers\n", | ||
"filtered_df_init[\"Estimate\"] = filtered_df_init[\"Estimate\"].astype(float).astype(int)\n", | ||
"display(HTML(filtered_df_init.to_html(escape=False, index=False)))\n", | ||
" # Calculate the total of the \"Estimate\" column and convert it to an integer\n", | ||
" total_estimate = int(filtered_df_init[\"Estimate\"].astype(float).sum())\n", | ||
"\n", | ||
" # Display the DataFrame with clickable links and format the \"Estimate\" column as integers\n", | ||
" filtered_df_init[\"Estimate\"] = filtered_df_init[\"Estimate\"].astype(float).astype(int)\n", | ||
" filtered_df_init[\"Email_List\"] = filtered_df_init[\"Title\"] + \": \" + filtered_df_init[\"Estimate\"].astype(str)\n", | ||
" display(HTML(filtered_df_init.to_html(escape=False, index=False)))\n", | ||
"\n", | ||
"# Separate sentence with emoji for the total estimate\n", | ||
"print(f\"\\n🚀 The total estimate is: {total_estimate}\")" | ||
|
@@ -425,30 +451,37 @@ | |
"logo_img = \"https://landen.imgix.net/jtci2pxwjczr/assets/5ice39g4.png?w=186\"\n", | ||
"\n", | ||
"# Get the current date\n", | ||
"current_date = datetime.now().strftime(\"%B %Y\")\n", | ||
"\n", | ||
"# Center-align the content of the email\n", | ||
"center_style = 'style=\"text-align: center;\"'\n", | ||
"scenario_display = scenario_filter.strftime(\"%B %Y\")\n", | ||
"\n", | ||
"# Convert the \"Estimate\" column values to string\n", | ||
"filtered_df_init[\"Estimate\"] = filtered_df_init[\"Estimate\"].astype(str)\n", | ||
"sum_estimates = 0\n", | ||
"list_issues = []\n", | ||
"if len(filtered_df_init) > 0:\n", | ||
" sum_estimates = filtered_df_init[\"Estimate\"].astype(int).sum()\n", | ||
" list_issues = [emailbuilder.link(row[\"PR URL\"], row[\"Email_List\"]) for index, row in filtered_df_init.iterrows()]\n", | ||
"list_prs = [emailbuilder.link(pr.html_url, pr.title) for pr in reviewed_pr]\n", | ||
"\n", | ||
"reward_issues_merged = sum_estimates * 5\n", | ||
"reward_prs_reviewed = len(list_prs) * 10\n", | ||
"rewards = reward_issues_merged + reward_prs_reviewed\n", | ||
"\n", | ||
"# Define the content for the email using the email builder\n", | ||
"email_content = {\n", | ||
" \"element\": naas_drivers.emailbuilder.title(f'<div {center_style}>📝 Report on templates created for {contributor_profile}</div>'),\n", | ||
" \"heading\": naas_drivers.emailbuilder.heading(f'<div {center_style}>Monthly Report - {current_date}</div>'),\n", | ||
" \"text\": naas_drivers.emailbuilder.text(f'<div {center_style}>Here is the summary of your contributions for this month</div>'),\n", | ||
" \"table\": naas_drivers.emailbuilder.table(filtered_df_init, header=True, border=True),\n", | ||
" \"total_estimate\": naas_drivers.emailbuilder.text(f'<div {center_style}><b>Total Estimate: {total_estimate}</b></div>'),\n", | ||
" \"footer\": naas_drivers.emailbuilder.footer_company(\n", | ||
" networks=[{\"img_src\": logo_img, \"href\": \"\"}],\n", | ||
" company=[\"Company Informations\"],\n", | ||
" legal=[\"Legal Informations\"],\n", | ||
" ),\n", | ||
"# \"element\": naas_drivers.emailbuilder.title(f'<div {center_style}>📝 Report on templates created for {contributor_profile}</div>'),\n", | ||
" \"heading\": emailbuilder.heading(f'Monthly Report - {scenario_display}'),\n", | ||
" \"intro\": emailbuilder.text(f\"Hi {contributor_profile},\"),\n", | ||
" \"reward\": emailbuilder.text(f'💵 Here is the reward your contribution as templates maintainer: {rewards}$'),\n", | ||
" \"reward_det\": emailbuilder.list([f\"Issues closed by PRs merged: {reward_issues_merged} $\", f\"PRs reviewed: {reward_prs_reviewed} $\"]),\n", | ||
" \"contrib\": emailbuilder.text(f'Please find below the detail of your contributions.'),\n", | ||
" \"assign\": emailbuilder.text(f'🧑💻 Issues closed by PRs merged: {len(filtered_df_init)} (Total estimates: {sum_estimates})'),\n", | ||
" \"assign_list\": emailbuilder.list(list_issues),\n", | ||
" \"review\": emailbuilder.text(f'👀💻 PRs reviewed: {len(reviewed_pr)}'),\n", | ||
" \"review_list\": emailbuilder.list(list_prs),\n", | ||
" \"footer\": emailbuilder.footer_company(naas=True),\n", | ||
"}\n", | ||
"\n", | ||
"# Generate the email content as HTML\n", | ||
"content = naas_drivers.emailbuilder.generate(display=\"iframe\", **email_content)" | ||
"content = emailbuilder.generate(display=\"iframe\", **email_content)" | ||
] | ||
}, | ||
{ | ||
|