Skip to content
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

Add sponsors config, output, and tests #26

Merged
merged 1 commit into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env-example
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ ORGANIZATION = "organization"
START_DATE = ""
END_DATE = ""
GH_ENTERPRISE_URL=" "
SPONSOR_INFO = "False"
1 change: 1 addition & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ ignore=.git,
disable=
line-too-long,
too-many-arguments,
duplicate-code,
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ Below are the allowed configuration options:
| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want the contributor information from. ie. `github/contributors` |
| `START_DATE` | False | Beginning of time | The date from which you want to start gathering contributor information. ie. Aug 1st, 2023 would be `2023-08-01` If `start_date` and `end_date` are specified then the action will determine if the contributor is new. A new contributor is one that has contributed in the date range specified but not before the start date. **Performance Note:** Using start and end dates will reduce speed of the action by approximately 63X. ie without dates if the action takes 1.7 seconds, it will take 1 minute and 47 seconds.|
| `END_DATE` | False | Current Date | The date at which you want to stop gathering contributor information. Must be later than the `START_DATE`. ie. Aug 2nd, 2023 would be `2023-08-02` If `start_date` and `end_date` are specified then the action will determine if the contributor is new. A new contributor is one that has contributed in the date range specified but not before the start date. |
| `SPONSOR_INFO` | False | False | If you want to include sponsor information in the output. This will include the sponsor count and the sponsor URL. This will impact action performance. ie. SPONSOR_INFO = "False" or SPONSOR_INFO = "True" |

### Example workflow
### Example workflows

```yaml
name: Monthly contributor report
Expand Down Expand Up @@ -122,13 +123,13 @@ jobs:

- Organization: super-linter

| Total Contributors | Total Contributions |
| --- | --- |
| 1 | 210 |

| Username | Contribution Count | Commits |
| Total Contributors | Total Contributions | % new contributors |
| --- | --- | --- |
| zkoppert | 210 | [super-linter/super-linter](https://github.com/super-linter/super-linter/commits?author=zkoppert) |
| 1 | 1913 | 0% |

| Username | Contribution Count | New Contributor | Sponsor URL | Commits |
| --- | --- | --- | --- | --- |
| zkoppert | 1913 | False | [Sponsor Link](https://github.com/sponsors/zkoppert) | [super-linter/super-linter](https://github.com/super-linter/super-linter/commits?author=zkoppert&since=2021-09-01&until=2023-09-30) |

## Local usage without Docker

Expand Down
60 changes: 58 additions & 2 deletions contributor_stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
# "avatar_url" : "https://avatars.githubusercontent.com/u/29484535?v=4",
# "contribution_count" : 1261,
# "commit_url" : "https://github.com/github/contributors/commits?author=zkoppert&since=2023-10-01&until=2023-10-05"
# "sponsor_info" : "https://github.com/sponsors/zkoppert"
# }
# ]


import requests


class ContributorStats:
"""
A class to represent a contributor_stats object correlating to a single contributors stats.
Expand All @@ -21,6 +25,7 @@ class ContributorStats:
avatar_url (str): The url of the contributor's avatar
contribution_count (int): The number of contributions the contributor has made
commit_url (str): The url of the contributor's commits
sponsor_info (str): The url of the contributor's sponsor page

"""

Expand All @@ -35,6 +40,7 @@ def __init__(
avatar_url: str,
contribution_count: int,
commit_url: str,
sponsor_info: str,
):
"""Initialize the contributor_stats object"""
new_contributor = False
Expand All @@ -43,6 +49,7 @@ def __init__(
self.avatar_url = avatar_url
self.contribution_count = contribution_count
self.commit_url = commit_url
self.sponsor_info = sponsor_info

def __repr__(self) -> str:
"""Return the representation of the contributor_stats object"""
Expand All @@ -51,6 +58,7 @@ def __repr__(self) -> str:
f"new_contributor={self.new_contributor}, "
f"avatar_url={self.avatar_url}, "
f"contribution_count={self.contribution_count}, commit_url={self.commit_url})"
f"sponsor_info={self.sponsor_info})"
)

def __eq__(self, other) -> bool:
Expand All @@ -77,8 +85,8 @@ def is_new_contributor(username: str, returning_contributors: list) -> bool:
"""
for contributor in returning_contributors:
if username in contributor.username:
return True
return False
return False
return True


def merge_contributors(contributors: list) -> list:
Expand Down Expand Up @@ -118,3 +126,51 @@ def merge_contributors(contributors: list) -> list:
merged_contributors.append(contributor)

return merged_contributors


def get_sponsor_information(contributors: list, token: str) -> list:
"""
Get the sponsor information for each contributor

Args:
contributors (list): A list of ContributorStats objects
github_connection (object): The authenticated GitHub connection object from PyGithub

Returns:
contributors (list): A list of ContributorStats objects with sponsor information
"""
for contributor in contributors:
# query the graphql api for the user's sponsor information
query = """
query($username: String!){
repositoryOwner(login: $username) {
... on User {
hasSponsorsListing
}
}
}
"""
variables = {"username": contributor.username}

# Send the GraphQL request
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
"https://api.github.com/graphql",
json={"query": query, "variables": variables},
headers=headers,
timeout=60,
)

# Check for errors in the GraphQL response
if response.status_code != 200 or "errors" in response.json():
raise ValueError("GraphQL query failed")

data = response.json()["data"]

# if the user has a sponsor page, add it to the contributor object
if data["repositoryOwner"]["hasSponsorsListing"]:
contributor.sponsor_info = (
f"https://github.com/sponsors/{contributor.username}"
)

return contributors
22 changes: 20 additions & 2 deletions contributors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ def main():
"""Run the main program"""

# Get environment variables
organization, repository, token, ghe, start_date, end_date = env.get_env_vars()
(
organization,
repository,
token,
ghe,
start_date,
end_date,
sponsor_info,
) = env.get_env_vars()

# Auth to GitHub.com
github_connection = auth.auth_to_github(token, ghe)
Expand Down Expand Up @@ -40,10 +48,19 @@ def main():
contributor.username, returning_contributors
)

# Get sponsor information on the contributor
if sponsor_info == "true":
contributors = contributor_stats.get_sponsor_information(contributors, token)
# Output the contributors information
# print(contributors)
markdown.write_to_markdown(
contributors, "contributors.md", start_date, end_date, organization, repository
contributors,
"contributors.md",
start_date,
end_date,
organization,
repository,
sponsor_info,
)
# write_to_json(contributors)

Expand Down Expand Up @@ -136,6 +153,7 @@ def get_contributors(
user.avatar_url,
user.contributions_count,
commit_url,
"",
)
contributors.append(contributor)

Expand Down
15 changes: 12 additions & 3 deletions env.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
"""A GitHub Action that given an organization or repository, produces information about the contributors over the specified time period."""

import os
from typing import List
from os.path import dirname, join
from dotenv import load_dotenv


def get_env_vars() -> tuple[str, str, List[str]]:
def get_env_vars() -> tuple[str, str, str, str, str, str, str]:
"""
Get the environment variables for use in the action.

Expand All @@ -20,6 +19,8 @@ def get_env_vars() -> tuple[str, str, List[str]]:
str: the GitHub Enterprise URL to use for authentication
str: the start date to get contributor information from
str: the end date to get contributor information to.
str: whether to get sponsor information on the contributor

"""
# Load from .env file if it exists
dotenv_path = join(dirname(__file__), ".env")
Expand Down Expand Up @@ -50,4 +51,12 @@ def get_env_vars() -> tuple[str, str, List[str]]:
if end_date and len(end_date) != 10:
raise ValueError("END_DATE environment variable not in the format YYYY-MM-DD")

return organization, repository, token, ghe, start_date, end_date
sponsor_info = os.getenv("SPONSOR_INFO")
# make sure that sponsor_string is a boolean
sponsor_info = sponsor_info.lower().strip()
if sponsor_info not in ["true", "false", ""]:
raise ValueError(
"SPONSOR_INFO environment variable not a boolean. ie. True or False or blank"
)

return organization, repository, token, ghe, start_date, end_date, sponsor_info
42 changes: 30 additions & 12 deletions markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@


def write_to_markdown(
collaborators, filename, start_date, end_date, organization, repository
collaborators,
filename,
start_date,
end_date,
organization,
repository,
sponsor_info,
):
"""
This function writes a list of collaborators to a markdown file in table format.
Expand All @@ -17,14 +23,15 @@ def write_to_markdown(
end_date (str): The end date of the date range for the contributor list.
organization (str): The organization for which the contributors are being listed.
repository (str): The repository for which the contributors are being listed.
sponsor_info (str): True if the user wants the sponsor_url shown in the report

Returns:
None

"""
# Put together the contributor table
table, total_contributions = get_contributor_table(
collaborators, start_date, end_date, organization, repository
collaborators, start_date, end_date, organization, repository, sponsor_info
)

# Put together the summary table including # of new contributions, # of new contributors, % new contributors, % returning contributors
Expand Down Expand Up @@ -91,7 +98,7 @@ def get_summary_table(collaborators, start_date, end_date, total_contributions):

"""
if start_date and end_date:
summary_table = "| Total Contributors | Total Contributions | % new contributors |\n| --- | --- | --- |\n"
summary_table = "| Total Contributors | Total Contributions | % New Contributors |\n| --- | --- | --- |\n"
summary_table += (
"| "
+ str(len(collaborators))
Expand All @@ -106,7 +113,7 @@ def get_summary_table(collaborators, start_date, end_date, total_contributions):
2,
)
)
+ "% | \n\n"
+ "% |\n\n"
)
else:
summary_table = "| Total Contributors | Total Contributions |\n| --- | --- |\n"
Expand All @@ -118,7 +125,7 @@ def get_summary_table(collaborators, start_date, end_date, total_contributions):


def get_contributor_table(
collaborators, start_date, end_date, organization, repository
collaborators, start_date, end_date, organization, repository, sponsor_info
):
"""
This function returns a string containing a markdown table of the contributors and the total contribution count.
Expand All @@ -130,16 +137,22 @@ def get_contributor_table(
end_date (str): The end date of the date range for the contributor list.
organization (str): The organization for which the contributors are being listed.
repository (str): The repository for which the contributors are being listed.
sponsor_info (str): True if the user wants the sponsor_url shown in the report

Returns:
table (str): A string containing a markdown table of the contributors and the total contribution count.
total_contributions (int): The total number of contributions made by all of the contributors.

"""
columns = ["Username", "Contribution Count"]
if start_date and end_date:
headers = "| Username | Contribution Count | New Contributor | Commits |\n| --- | --- | --- | --- |\n"
else:
headers = "| Username | Contribution Count | Commits |\n| --- | --- | --- |\n"
columns += ["New Contributor"]
if sponsor_info == "true":
columns += ["Sponsor URL"]
columns += ["Commits"]

headers = "| " + " | ".join(columns) + " |\n"
headers += "| " + " | ".join(["---"] * len(columns)) + " |\n"

table = headers
total_contributions = 0
Expand All @@ -162,10 +175,15 @@ def get_contributor_table(
commit_urls += url + ", "
new_contributor = collaborator.new_contributor

if start_date and end_date:
row = f"| {username} | {contribution_count} | {new_contributor} | {commit_urls} |\n"
else:
row = f"| {username} | {contribution_count} | {commit_urls} |\n"
row = f"| {username} | {contribution_count} |"
if "New Contributor" in columns:
row += f" {new_contributor} |"
if "Sponsor URL" in columns:
if collaborator.sponsor_info == "":
row += " not sponsorable |"
else:
row += f" [Sponsor Link]({collaborator.sponsor_info}) |"
row += f" {commit_urls} |\n"

table += row
return table, total_contributions
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
github3.py==4.0.1
python-dotenv==1.0.0
pytest==7.4.2
pytest-cov==4.1.0
pytest-cov==4.1.0
Requests==2.31.0
Loading