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

Sending PR notifications to project channels #1142

Open
surajkumar opened this issue Jul 28, 2024 · 0 comments · May be fixed by #1146 or #1143
Open

Sending PR notifications to project channels #1142

surajkumar opened this issue Jul 28, 2024 · 0 comments · May be fixed by #1146 or #1143
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers priority: normal

Comments

@surajkumar
Copy link
Contributor

surajkumar commented Jul 28, 2024

As a user that has a project posted in #projects I want to...

be able to link my GitHub repository to the project thread

So that...

I can receive pull request updates and stay informed about contributions.

Context

We have people posting projects within the discord server and for the ones that have active community development, to create enagagement and for continually receiving updates, we want notifications of PRs sent to their project channel.

Possible solution could be:

  1. The user setup may be a slash command e.g. /link-gh-project with the options: Repository Owner and Repository Name.
    In the backend, we can store the information [repositoryOwner, repositoryName, projectChannelId].
    A similar /unlink-gh-project to remove the association.

  2. Create a Routine that runs either every 15 minutes or every 1 hour to poll the GitHub API https://api.github.com/repo/<repositoryOwner>/<repositoryName> and send the PR information to the project channel on Discord.

The solution should ideally check the PR "created_at" field and if it is greater than the "last poll time", send information to discord.

The repository metadata obtained from /link-gh-project should be stored in the database.

The states to report on will most likely be [opened, merged, closed].

Considerations

  • When implementing the solution, consider GitHub and Discord rate limits. GitHub has a limit of 5,000 requests per hour.
  • To authenticate with GitHub a Personal Access Token (PAT) is required so ensure to handle that safely
  • Linking a project should only be performed by either staff members (community ambassadors, moderators and administrators) or the Project thread creator (OP)
  • Validating that we can access the PRs in situations such as the repo is not public.
  • Handling token errors if the PAT expires.

Out of Scope

Using the GitHub web hook functionality because of the complexity this introduces on the VPS.
Using Discord web hooks as that is not supported for forums.

Acceptance Criteria

GIVEN a user has linked their projects GitHub repository
THEN a message of new PRs and their statuses is sent to the project channel as an embed

To make life easier, here's some sample code I have written to get the contributor of this started:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

public class PullRequestFetcher {
    private static final Logger LOGGER = Logger.getLogger(PullRequestFetcher.class.getName());
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final String GITHUB_API_URL = "https://api.github.com";
    private final String githubPersonalAccessToken;
    private final HttpClient httpClient;

    public PullRequestFetcher(String githubPersonalAccessToken) {
        this.githubPersonalAccessToken = githubPersonalAccessToken;
        this.httpClient = HttpClient.newBuilder().build();
    }

    public List<PullRequest> fetchPullRequests(String repositoryOwner, String repositoryName) {
        List<PullRequest> pullRequests = new ArrayList<>();

        String repository = repositoryOwner + "/" + repositoryName;
        String apiURL = "%s/repos/%s/pulls".formatted(GITHUB_API_URL, repository);

        LOGGER.fine("Making request to " + apiURL);

        HttpRequest httpRequest = HttpRequest.newBuilder()
                .uri(URI.create(apiURL))
                .header("Accept", "application/vnd.github+json")
                .header("X-GitHub-Api-Version", "2022-11-28")
                .header("Authorization", githubPersonalAccessToken)
                .build();
        try {
            HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
            int statusCode = response.statusCode();

            LOGGER.fine("Received http status " + statusCode);

            if(statusCode != 200) {
                LOGGER.warning("Unexpected HTTP status "
                        + statusCode
                        + " while fetching pull requests for "
                        + repository + "\nbody="
                        + response.body());
            } else {
                try {
                    pullRequests = OBJECT_MAPPER.readValue(response.body(), new TypeReference<>() {});
                } catch (JsonProcessingException jpe) {
                    LOGGER.severe("Failed to parse JSON " + jpe.getMessage());
                }
            }

        } catch (IOException | InterruptedException e) {
            LOGGER.severe("Failed to fetch pull request from discord for "
                    + repository
                    + ":"
                    + e.getMessage());
        }

        return pullRequests;
    }
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record PullRequest(
        @JsonProperty("html_url") String htmlUrl,
        @JsonProperty("number") int number,
        @JsonProperty("state") String state,
        @JsonProperty("title") String title,
        @JsonProperty("user") User user,
        @JsonProperty("body") String body,
        @JsonProperty("created_at") String createdAt,
        @JsonProperty("draft") boolean draft
) {}
@JsonIgnoreProperties(ignoreUnknown = true)
public record User(
        @JsonProperty("name") String name,
        @JsonProperty("id") int id,
        @JsonProperty("avatar_url") String avatarUrl
) {}
@surajkumar surajkumar added the enhancement New feature or request label Jul 28, 2024
@surajkumar surajkumar linked a pull request Jul 29, 2024 that will close this issue
@SquidXTV SquidXTV linked a pull request Jul 31, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers priority: normal
Projects
Status: In Review
Development

Successfully merging a pull request may close this issue.

3 participants