Created
August 19, 2020 14:08
-
-
Save jlaura/a0c2a456bb2f2d9c31f178ae287fe870 to your computer and use it in GitHub Desktop.
A bot to manage issues on the USGS-Astrogeology ISIS3 repository.
This file contains hidden or 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
| import requests | |
| from datetime import datetime | |
| ### Config ### | |
| # A mapping between the label id needed by the API and the human readable name | |
| labelids = {'inactive': 'MDU6TGFiZWwyMjg1Mjc0MTc2', | |
| 'automatically_closed': 'MDU6TGFiZWwyMjg1Mjc2MjI3'} | |
| # Headers necessary for authentication | |
| APIKEY = "The ASC Bot's API Key" | |
| headers = {"Authorization": f"Bearer {APIKEY}"} | |
| # Messages to be posted by the bot at given intervals. | |
| first_message = """ | |
| I am a bot that cleans up old issues that do not have activity. | |
| This issue has not received feedback in the last six months. I am going to add the `inactive` label to | |
| this issue. If this is still a pertinent issue, please add a comment or add an emoji to an existing comment. | |
| I will post again in 11 months with another reminder and will close this issue on it's birthday unless it has | |
| some activity. | |
| """ | |
| second_message = """ | |
| I am a bot that cleans up old issues that do not have activity. | |
| This issue has not received feedback in the last eleven months! If this is still a pertinent issue, please add a comment or add an emoji to an existing comment. | |
| In one month will close this issue on it's birthday unless it has some activity. | |
| """ | |
| final_message = """ | |
| I am a bot that cleans up old issues that do not have activity. | |
| Happy Birthday to this issue! :birthday: | |
| Unfortunately, this issue has not received much attention in the last 12 months. Therefore, I am going to close it. Please feel free to reopen this issue or open a new issue sometime in the future. If this issue is a bug report, please check that the issue still exists in our newest version before reopening. | |
| """ | |
| def run_query(query): | |
| """ | |
| Runs a GraphQL query against the GitHub API. | |
| Parameters | |
| ---------- | |
| query : str | |
| The GraphQL string query to be passed to the API | |
| Returns | |
| ------- | |
| : dict | |
| The JSON (dict) response from the API | |
| """ | |
| request = requests.post('https://api.github.com/graphql', json={'query':query}, headers=headers) | |
| if request.status_code == 200: | |
| return request.json() | |
| else: | |
| raise Exception("Query failed to run by returning code of {}. {}".format(request.status_code, query)) | |
| def get_issues(): | |
| """ | |
| Get all of the open issues in a repository. | |
| Returns | |
| ------- | |
| : dict | |
| GitHub API response parsed to the individual issues (through | |
| edges and nodes in the response). | |
| """ | |
| # Query to get issues, comments, and reactions to comments | |
| query = f""" | |
| query {{ | |
| repository(owner:"USGS-Astrogeology", name:"ISIS3") {{ | |
| openIssues: issues(states: OPEN, last:1) {{ | |
| edges {{ | |
| node {{ | |
| id | |
| title | |
| updatedAt | |
| createdAt | |
| url | |
| comments(last:100) {{ | |
| edges {{ | |
| node {{ | |
| author {{login}} | |
| updatedAt | |
| createdAt | |
| reactions(last:100) {{ | |
| edges {{ | |
| node {{ | |
| createdAt | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| }} | |
| """ | |
| response = run_query(query) # Execute the query | |
| result = response['data']['repository']['openIssues']['edges'] | |
| return [issue['node'] for issue in result] | |
| def find_most_recent_activity(issue): | |
| """ | |
| This func finds the most recent activity on an issue by iterating over all of | |
| the content, omitting any posts by the bot, and finding the most recent UTC | |
| timestamp. | |
| Parameters | |
| ---------- | |
| issue : dict | |
| The JSON (dict) response from the github API for a single issue. We | |
| assume that the 'node' key has been omitted. | |
| Returns | |
| ------- | |
| age : obj | |
| A datetime.timedelta object | |
| """ | |
| dates = [] | |
| # Intentionally skip the last updated at key because this could be the bot talking. | |
| dates.append(issue['createdAt']) | |
| # Step over all the comments; ignore the bot and reactions to the bot. | |
| for comment in issue['comments']['edges']: | |
| comment = comment['node'] | |
| if comment['author']['login'] == 'ascbot': | |
| continue | |
| dates.append(comment['updatedAt']) | |
| dates.append(comment['createdAt']) | |
| # Step over any reactions | |
| for reaction in comment['reactions']['edges']: | |
| reaction = reaction['node'] | |
| dates.append(reaction['createdAt']) | |
| newest_activity = max(dates) | |
| age = datetime.utcnow() - datetime.strptime(newest_activity, '%Y-%m-%dT%H:%M:%SZ') | |
| return age | |
| def update_with_message(issueid, msg): | |
| """ | |
| Using the Github V4 GraphQL API, update an issue | |
| with a given message. | |
| Parameters | |
| ---------- | |
| issueid : str | |
| The GitHub hashed issue identifier | |
| msg : str | |
| The string message to be posted by the API key holder | |
| Returns | |
| ------- | |
| : dict | |
| The JSON (dict) response from the GitHub API | |
| """ | |
| # Add a comment query | |
| query = f""" | |
| mutation {{ | |
| addComment(input:{{ | |
| subjectId: "{issueid}", | |
| body:"{msg}" | |
| }}) {{ | |
| clientMutationId | |
| }} | |
| }} | |
| """ | |
| return run_query(query) | |
| def add_label(issueid, labelid): | |
| """ | |
| Using the Github V4 GraphQL API, add a | |
| label to an issue. | |
| Parameters | |
| ---------- | |
| issueid : str | |
| The GitHub hashed issue identifier | |
| labelid : str | |
| The GitHub hashed label identifier | |
| Returns | |
| ------- | |
| : dict | |
| The JSON (dict) response from the GitHub API | |
| """ | |
| query = f"""mutation {{ | |
| addLabelsToLabelable(input:{{ | |
| labelableId:"{issueid}", | |
| labelIds:["{labelid}"] | |
| }}) {{ | |
| clientMutationId | |
| }} | |
| }}""" | |
| return run_query(query) | |
| def close_issue(issueid): | |
| """ | |
| Using the Github V4 GraphQL API, close | |
| an issue. | |
| Parameters | |
| ---------- | |
| issueid : str | |
| The GitHub hashed issue identifier | |
| Returns | |
| ------- | |
| : dict | |
| The JSON (dict) response from the GitHub API | |
| """ | |
| query = f"""mutation {{ | |
| closeIssue(input:{{ | |
| issueId:"{issueid}" | |
| }}) {{ | |
| clientMutationId | |
| }} | |
| }}""" | |
| return run_query(query) | |
| def find_and_update_inactive_issues(issues): | |
| """ | |
| Parse a list of of GitHub API response issues and | |
| update those issue which meet the inactivity criteria. | |
| Issues with no activity in the last 182 are updated with | |
| the `inactive` label and a message. | |
| Issues with no activity in 335 days are updated with a | |
| nudge message. | |
| Issues with no activity after 365 days are closed with | |
| a message and an `automatically_closed` label. | |
| Parameters | |
| ---------- | |
| issues : list | |
| of JSON (dict) issues from the GitHub API parsed | |
| down to the individual nodes (issues) | |
| """ | |
| for issue in open_issues: | |
| age = find_most_recent_activity(issue) | |
| if age.days > 365: | |
| resp = update_with_message(issue['id'], final_message) | |
| resp = add_label(issue['id'], labelids['automatically_closed']) | |
| resp = close_issue[issue['id']] | |
| elif age.days > 335: | |
| resp = update_with_message(issue['id'], second_message) | |
| elif age.days > 182: | |
| resp = update_with_message(issue['id'], first_message) | |
| resp = add_label(issue['id'], labelids['inactive']) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment