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

Create separate threads on GitHub Discussions #5

Draft
wants to merge 11 commits into
base: develop
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/PENDING_REVIEW_NOTIFICATION_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Hi {{ username }},

It looks like you're assigned to this PR, but haven't taken any action for at least 2 days:
The following PRs are currently blocked on your review:
{{ pr_list }}

Please review and unassign yourself from the pending PRs as soon as possible.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep this line, thanks. Let's also change it to: "Please review and unassign yourself from the pending PRs as soon as possible, then mark this discussion thread as 'Done'."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Please review and unassign yourself from the pending PRs as soon as possible, then mark this discussion thread as 'Done'.

To avoid these messages in the future, please bookmark [this link](https://github.com/pulls/assigned) and check it daily. Thanks!
241 changes: 102 additions & 139 deletions src/github_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import datetime
import logging

from typing import Any, Callable, DefaultDict, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Union
from dateutil import parser
import requests
from src import github_domain
Expand Down Expand Up @@ -203,30 +203,17 @@ def get_pull_request_dict_with_timestamp(
assignee['created_at'] = parser.parse(event['created_at'])
return pr_dict


@check_token
def _get_discussion_data(
def _get_repository_id(
org_name: str,
repo_name: str,
discussion_category: str,
discussion_title: str,
) -> Tuple[str, int]:
"""Fetch discussion data from api and return corresponding discussion id and
discussion number.
"""
) -> str:
"""Fetch repository id from given org and repo and return the id."""

# The following query is written in GraphQL and is being used to fetch the category
# ids and titles from the GitHub discussions. To learn more, check this out
# https://docs.github.com/en/graphql.
query = """
query ($org_name: String!, $repository: String!) {
repository(owner: $org_name, name: $repository) {
discussionCategories(first: 10) {
nodes {
id
name
}
}
id
}
}
"""
Expand All @@ -244,32 +231,33 @@ def _get_discussion_data(
)
data = response.json()

category_id = None
discussion_categories = (
data['data']['repository']['discussionCategories']['nodes'])

for category in discussion_categories:
if category['name'] == discussion_category:
category_id = category['id']
break
repository_id: str = (
data['data']['repository']['id'])

if category_id is None:
if repository_id is None:
raise builtins.BaseException(
f'{discussion_category} category is missing in GitHub Discussion.')
f'{org_name}/{repo_name} doesn\'t exist.')

# The following query is written in GraphQL and is being used to fetch discussions
# from a particular GitHub discussion category. This helps to find out the discussion
# where we want to comment. To learn more, check this out
# https://docs.github.com/en/graphql.
return repository_id

@check_token
def _get_category_id(
org_name: str,
repo_name: str,
discussion_category: str
) -> str:
"""Fetch discussion category id from given category name and return the id."""

# The following query is written in GraphQL and is being used to fetch the category
# ids and titles from the GitHub discussions. To learn more, check this out
# https://docs.github.com/en/graphql.
query = """
query ($org_name: String!, $repository: String!, $category_id: ID!) {
query ($org_name: String!, $repository: String!) {
repository(owner: $org_name, name: $repository) {
discussions(categoryId: $category_id, last:10) {
discussionCategories(first: 10) {
nodes {
id
title
number
name
}
}
}
Expand All @@ -278,8 +266,7 @@ def _get_discussion_data(

variables = {
'org_name': org_name,
'repository': repo_name,
'category_id': category_id
'repository': repo_name
}

response = requests.post(
Expand All @@ -289,52 +276,50 @@ def _get_discussion_data(
timeout=TIMEOUT_SECS
)
data = response.json()
discussion_id = None

discussions = data['data']['repository']['discussions']['nodes']
category_id: Optional[str] = None
discussion_categories = (
data['data']['repository']['discussionCategories']['nodes'])

for discussion in discussions:
if discussion['title'] == discussion_title:
discussion_id = discussion['id']
discussion_number = discussion['number']
for category in discussion_categories:
if category['name'] == discussion_category:
category_id = category['id']
break

if discussion_id is None:
if category_id is None:
raise builtins.BaseException(
f'Discussion with title {discussion_title} not found, please create a '
'discussion with that title.')

return discussion_id, discussion_number
f'{discussion_category} category is missing in GitHub Discussion.')

assert category_id is not None
return category_id

def _get_past_time(days: int=60) -> str:
"""Returns the subtraction of current time and the arg passed in days."""
return (
datetime.datetime.now(
datetime.timezone.utc) - datetime.timedelta(days=days)).strftime(
'%Y-%m-%dT%H:%M:%SZ')


def _get_old_comment_ids(
@check_token
def _get_discussion_ids(
org_name: str,
repo_name: str,
discussion_number: int
discussion_category: str,
) -> List[str]:
"""Return the old comment ids."""
"""Fetch discussion data from api and return corresponding discussion id and
discussion number.
"""

category_id = _get_category_id(org_name, repo_name, discussion_category)

# The following query is written in GraphQL and is being used to fetch discussions
# from a particular GitHub discussion category. This helps to find out the discussion
# where we want to comment. To learn more, check this out
# https://docs.github.com/en/graphql.

# The following query is written in GraphQL and is being used to fetch the oldest 50
# comments in a existing GitHub discussions. Assuming that, this workflow will run
# twice every week, we will may not have more than 50 comments to delete. To learn
# more, check this out https://docs.github.com/en/graphql.
query = """
query ($org_name: String!, $repository: String!, $discussion_number: Int!) {
query ($org_name: String!, $repository: String!, $category_id: ID!) {
repository(owner: $org_name, name: $repository) {
discussion(number: $discussion_number) {
comments(first: 50) {
nodes {
id
createdAt
}
discussions(categoryId: $category_id, last:10) {
nodes {
id
title
number
}
}
}
Expand All @@ -344,7 +329,7 @@ def _get_old_comment_ids(
variables = {
'org_name': org_name,
'repository': repo_name,
'discussion_number': discussion_number
'category_id': category_id
}

response = requests.post(
Expand All @@ -353,44 +338,35 @@ def _get_old_comment_ids(
headers=_get_request_headers(),
timeout=TIMEOUT_SECS
)

response.raise_for_status()
data = response.json()

comment_ids: List[str] = []

discussion_comments = (
data['data']['repository']['discussion']['comments']['nodes']
)

# Delete comments posted before this time.
delete_comments_before_in_days = _get_past_time(DELETE_COMMENTS_BEFORE_IN_DAYS)
discussions = data['data']['repository']['discussions']['nodes']
discussion_ids = [
discussion['id'] for discussion in discussions if discussion['id'] is not None
]

for comment in discussion_comments:
if comment['createdAt'] < delete_comments_before_in_days:
comment_ids.append(comment['id'])
else:
break

return comment_ids
if not discussion_ids:
logging.info('No existing discussions found')

return discussion_ids

def _delete_comment(comment_id: str) -> None:
def _delete_discussion(discussion_id: str) -> None:
"""Delete the GitHub Discussion comment related to the comment id."""

query = """
mutation deleteComment($comment_id: ID!) {
deleteDiscussionComment(input: {id: $comment_id}) {
clientMutationId
comment {
bodyText
mutation deleteDiscussion($discussion_id: ID!) {
deleteDiscussion(input: {id: $discussion_id}) {
clientMutationId,
discussion {
title
}
}
}
"""

variables = {
'comment_id': comment_id
'discussion_id': discussion_id
}

response = requests.post(
Expand All @@ -402,27 +378,47 @@ def _delete_comment(comment_id: str) -> None:
response.raise_for_status()


def _post_comment(discussion_id: str, message: str) -> None:
"""Post the given message in an existing discussion."""
@check_token
def delete_discussions(
org_name: str,
repo_name: str,
discussion_category: str,
) -> None:
"""Delete all existing discussions in the given discussion category."""

# The following code is written in GraphQL and is being used to perform a mutation
# operation. More specifically, we are using it to comment in GitHub discussion to
# let reviewers know about some of their pending tasks. To learn more, check this out:
# https://docs.github.com/en/graphql.
discussion_ids = _get_discussion_ids(
org_name, repo_name, discussion_category)

for discussion_id in discussion_ids:
_delete_discussion(discussion_id)

@check_token
def create_discussion(
org_name: str,
repo_name: str,
discussion_category: str,
discussion_title: str,
discussion_body: str
) -> None:
"""Create a new discussion with the given title and body in the given discussion category."""

category_id = _get_category_id(org_name, repo_name, discussion_category)
repo_id = _get_repository_id(org_name, repo_name)
query = """
mutation post_comment($discussion_id: ID!, $comment: String!) {
addDiscussionComment(input: {discussionId: $discussion_id, body: $comment}) {
clientMutationId
comment {
mutation createDiscussion($repo_id: ID!, $category_id: ID!, $title: String!, $body: String!) {
createDiscussion(input: {repositoryId: $repo_id, categoryId: $category_id, title: $title, body: $body}) {
discussion {
id
}
}
}
"""

variables = {
'discussion_id': discussion_id,
'comment': message
'repo_id': repo_id,
'category_id': category_id,
'title': discussion_title,
'body': discussion_body
}

response = requests.post(
Expand All @@ -431,38 +427,5 @@ def _post_comment(discussion_id: str, message: str) -> None:
headers=_get_request_headers(),
timeout=TIMEOUT_SECS
)
response.raise_for_status()


@check_token
def delete_discussion_comments(
org_name: str,
repo_name: str,
discussion_category: str,
discussion_title: str
) -> None:
"""Delete old comments from GitHub Discussion."""

_, discussion_number = _get_discussion_data(
org_name, repo_name, discussion_category, discussion_title)

comment_ids = _get_old_comment_ids(org_name, repo_name, discussion_number)

for comment_id in comment_ids:
_delete_comment(comment_id)


@check_token
def add_discussion_comments(
org_name: str,
repo_name: str,
discussion_category: str,
discussion_title: str,
message: str
) -> None:
"""Add comments in an existing GitHub discussion."""

discussion_id, _ = _get_discussion_data(
org_name, repo_name, discussion_category, discussion_title)

_post_comment(discussion_id, message)
response.raise_for_status()
Loading
Loading