Code Monkey home page Code Monkey logo

pr-commenter-action's Introduction

PR Commenter Action

This GitHub action posts comments on a PR that can vary depending on which files are being changed in the PR.

Getting Started

Create workflow

Create your workflow file .github/workflows/pr-commenter.yml as follows.

name: "PR Commenter"
on:
  - pull_request_target

jobs:
  pr-comment:
    runs-on: ubuntu-latest
    steps:
      - uses: exercism/[email protected]
        with:
          github-token: "${{ github.token }}"
          config-file: ".github/pr-commenter.yml"

Create configuration file

Create your action configuration file .github/pr-commenter.yml as follows.

comment:
  on-update: recreate
  header: |
    Thank you for contributing to this repository :tada:.

  footer: |
    ---
    Automated comment created by [PR Commenter](https://github.com/exercism/pr-commenter-action) :robot:.

  snippets:
    - id: any-markdown-file-changed
      files:
        - '*.md'
        - '**/*.md'
      body: |
        It looks like you're changing a Markdown file.
        Make sure your changes follow our [language guidelines](some-link) when writing documentation.

Reference

Workflow inputs

github-token

Auth token used to manage issues or pull requests.

Required: true

Default: ${{ github.token }}

config-file

Required: true

Default: .github/pr-commenter.yml

To reference a config file in another repo use the format: <owner>/<repo>@<ref>:<path>, for example someuser/my-repo@v1:.github/pr-commenter.yml. NOTE: make sure that "my-repo" is public and has "Workflow permissions" allowing files to be read.

Configuration file

comment.on-create

Dictates what should happen if there is no comment on this PR yet. For almost all use cases, you want to keep the default behavior. The custom nothing option makes sense if you're using this action twice on the same PR, and you want the second execution not to create a new comment, but only edit it if it already exists from the first execution.

  • create - create a new comment
  • nothing - do not create a new comment

Also accepts a Mustache template that evaluates to one of the above values.

Required: false

Default: create

comment.on-update

Dictates what should happen if a comment was already created on this PR, but more changes were pushed to the PR and the comment needs to change.

  • recreate - delete the old comment and create a new one
  • edit - edit the old comment
  • nothing - leave the old comment unchanged

Also accepts a Mustache template that evaluates to one of the above values.

Required: true

Default: recreate

comment.header

An optional text to be included at the beginning of each comment.

Required: false

comment.footer

An optional text to be included at the end of each comment.

Required: false

comment.snippets

A list of comment snippet configurations. At least one snippet is required. Note that a PR comment will only be created if at least one of the snippets match, even if comment.header and/or comment.footer are given.

Required: true

comment.snippets[].id

A string consisting of letters, numbers, -, and _ or a Mustache template that evaluates to such a string.

Snippet ids are used to check whether a comment's content changed. If you're using a template variable in the snippets's body and you want to recreate the whole comment when that variable changes value, use it in the snippet's id too.

Required: true

comment.snippets[].body

The text to be included in the PR comment.

Required: true

Templates

Comment snippet bodies and ids (as well as comment.on-create, comment.on-update, comment.header, and comment.footer) are Mustache templates.

Variables for the template can be provided via the template-variables input which should be a string containing a valid JSON.

You can use the context and expression syntax to assemble the JSON and set-output to calculate data for the template in separate steps.

Example 1
name: "PR Commenter"
on:
  - pull_request_target

jobs:
  pr-comment:
    runs-on: ubuntu-latest
    steps:
      - name: Calculate some template variables
        id: vars
        run: |
          echo ::set-output name=today::"$(date +%d-%m-%Y)"

      - uses: exercism/[email protected]
        with:
          template-variables: |
            {
              "today": "${{ steps.vars.outputs.today }}",
              "prAuthor": "${{ github.event.pull_request.user.login }}",
              "branchNamePrefix": ${{ startsWith(github.event.pull_request.head.ref, 'ref-') }}
            }
comment:
  header: |
    Hi {{ prAuthor }}! Thank you for your contribution.

    {{^branchNamePrefix}}Your branch name doesn't start with the required prefix 'ref-'.{{/branchNamePrefix}}

Note that values such as the PR's title, body, or branch name should be considered unsafe user input.

Example 2

Here's a more complex example of using template variables. Let's say you have a multiline file that changes often, and you want to always include the newest content of the file in the snippet.

To ensure the comment will be recreated when the file changes, use the file's hash in the snippet's id.

To ensure that newline characters are handled correctly, use environment variables instead of job outputs and toJSON.

name: "PR Commenter"
on:
  - pull_request_target

jobs:
  pr-comment:
    runs-on: ubuntu-latest
    steps:
      - name: Set environment variables
        run: |
          IMPORTANT_FILE_CONTENT=$(cat important_file)
          echo "IMPORTANT_FILE_CONTENT<<EOF" >> $GITHUB_ENV
          echo "$IMPORTANT_FILE_CONTENT" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

      - uses: exercism/[email protected]
        with:
          template-variables: |
            {
              "importantFileContent": ${{ toJSON(env.IMPORTANT_FILE_CONTENT) }},
              "importnatFileHash": ${{ toJSON(hashFiles('important_file.txt')) }}
            }
comment:
  snippets:
    - id: snippet_{{ importnatFileHash }}}
    - files:
      - 'important_file.txt'
    - body: |
        This is very important:
        {{ importantFileContent }}

comment.snippets[].files

A list of globs (strings) and/or match objects. If at least one file changed in the PR matches at least one of the globs or match objects, this snippet's body will be included in the comment.

Globs

Example:

# any markdown file changed in any directory
comment:
  snippets:
    - id: any-markdown-file-changed
      files:
        - '*.md'
        - '**/*.md'
Match objects

A match object contains the keys any and/or all with a list of globs.

  • all - every file changed in this PR must match every glob in this list
  • any - at least one file changed in this PR must match every glob in this list

Example:

# at least one root-level markdown file changed
# and none of the changed files is the README.md
comment:
  snippets:
    - id: any-markdown-file-changed-but-readme
      files:
        - any: ['*.md']
          all: ['!README.md']

Required: true

comment.glob-options

This GitHub action uses the minimatch library for glob matching. A object with options can be provided under comment.glob-options to modify the behavior of this library. See the list of minimatch options for the list of supported options.

Example:

# Make all globs also match hidden files and directories
comment:
  glob-options:
    dot: true

Development

Setup

  • Install the required NodeJS version as specified in .tool-versions. The recommended way to manage multiple NodeJS versions is to use asdf.
  • Install the dependencies with npm install.
  • Run the tests with npm run test and the linter with npm run lint.

Authors

This library was originally created by @angelikatyborska. It is maintained by @angelikatyborska and the Exercism team. See the GitHub contributors graph for a full list of contributors.

pr-commenter-action's People

Contributors

angelikatyborska avatar dependabot[bot] avatar erikschierboom avatar exercism-bot avatar faisalafroz avatar ihid avatar lissy93 avatar oneideluizschneider avatar wespickett avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pr-commenter-action's Issues

branch name in the PR comment

use case: i want to use the PR commenter action to leave a comment on a PR with a link that changes depending on the base branch name.

I had a look at using environment variables, but I couldn't get it to work, so i thought i'd try creating an issue just in case theres something that can be implemented in the config file or something :) like a kind of pseudo env var potentially

MIT or AGPL3

@angelikatyborska Was there a specific reason you chose MIT for this over AGPL? If you explicitly want MIT then I'm fine with that (obviously, as it's your work), but if you don't have a preference, could we consider switching it to APGL3 before others contribute?

PR comment from a file

Hi ๐Ÿ‘‹ thanks for having this action, it is super helpful. I have one requirement, wondering if it already supports it. Is there a way to open a comment from a file path that is changed? I got the comment working on the PR, but I felt this could be used to notify which file we are warning the author about will be more helpful. Please let me know if it works already or if there is any work around for the same. Thanks.

Support on-update: edit-only

First of all thanks for providing and maintaining this great action!

Problem

I use this to communicate preview deployments to PRs.
Since the preview is shut down once a PR is closed, I also update the comment to remove the now dead link.

There are scenarios where the creation comment can not be created and the PR is then closed.
This currently creates a "The preview has been closed" comment, while there never had been a preview.

I want to prevent creation of this comment and currently to me it seems presence of the creation comment would be the easiest way to determine the behavior.

Proposed solution

For this it would be great if the action would support some kind of update-only behavior where nothing is done when no existing comment can be found. Also I'd need to switch the on-update behavior for the creation and deletion account.

My API proposal for this would be:

# .github/pr-commenter.yml
comment:
  on-update: {{ onUpdate }}
# .github/workflows/myFlow.yml
# ...
    steps:
      - ...shutdownPreview...
      - uses: exercism/[email protected]
        with:
          template-variables: |
            {
              "onUpdate": "edit-only",
            }

Happy to provide a PR if this sounds interesting to you. Also open for different API proposals.
Cheers ๐Ÿค™

Combining multiple snippets into single comment

Hey @angelikatyborska - Really enjoying your action :) I hadn't come across any similar actions, and this is a super useful task - awesome work!

Just a quick question, when multiple files patterns are matched, I would expect the comment to include all matching snippets between the header + footer, but instead it just includes the first one only. Am I missing something in my config maybe?

For reference, here's my config file and my action file.

Also I've noticed that the on-update option (recreate / edit /nothing) seems to do nothing. Each time whenever a new commit is pushed to the open PR, a new comment is always added.

Thank you very much,
Alicia :)

Show snippets depending on variable value

Would it be generally in scope for the tool to maybe in the future also support other ways than matching files to trigger a snippet?
E.g. we were thinking of showing a certain snippet when the PR title contains "typo".

We could set up a (template) variable "isTypoFix" in the workflow file. Then we would need some new key for the snippet instead of files, e.g.

  snippets:
    - id: some-id
      variable:
         value: {{ isTypoFix }}
         target: "true"  
      body: |
        Lorem ipsum ....

Maybe there is an easier way altogether implement something like this ...

Feel free to close the issue if this is out of scope.

Convert from JavaScript to TypeScript

It would be lovely for the maintainability of this project if it was written in TypeScript. The only reason I didn't initially write it in TypeScript is because I didn't know it then and I wanted to finish it faster (without adding in the additional time for learning TypeScript).

Optionally, allow patterns to match filenames starting with a period

Currently, patterns like **/* are not going to match paths like .docs/design.md. Exercism needs them to match, but it's not the default behavior of globs in many shells, so it needs to be configurable and turned off by default. I think it's enough if the option is global, it doesn't need to be configurable for every glob separately. I could be named something like comment.glob-dot?

To allow patterns to match filenames starting with a period, the option { dot: true } needs to be passed to the Minimatch constructor (https://github.com/isaacs/minimatch#dot).

TODO:

  • add config option (+ tests)
  • add config validation for the new option (+ tests)
  • release v1.1.0 after merge

Support "issue_comment" event

If an action is triggered via issue_comment, the PR number object might be different.

CI File

on:
  issue_comment:
    types:
      - created

jobs:
  build-comment:
    if: |
      github.event.issue.pull_request &&
        (github.event.comment.body == 'ping')
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v2

      - name: Comment to PR with zipped file
        uses: exercism/[email protected]
        with:
          github-token: ${{ github.token }}
          config-file: ".github/pr-commenter.yml"

Technical Details

The getPRNumber function needs to be updated

function getPrNumber() {

function getPrNumber() {
  const pullRequest = github.context.payload.pull_request || github.event.issue;
  if (!pullRequest) {
    return undefined;
  }

  return pullRequest.number;
}

GitHub Context Dump

{
    "event": {
      "action": "created",
      "comment": {
        "author_association": "CONTRIBUTOR",
        "body": "e2e",
        "created_at": "2022-04-26T08:47:20Z",
        "html_url": "https://github.com/org/repo-name/pull/173#issuecomment-1109524822",
        "id": 0,
        "issue_url": "https://api.github.com/repos/org/repo-name/issues/173",
        "node_id": "",
        "performed_via_github_app": null,
        "reactions": {
          "+1": 0,
          "-1": 0,
          "confused": 0,
          "eyes": 0,
          "heart": 0,
          "hooray": 0,
          "laugh": 0,
          "rocket": 0,
          "total_count": 0,
          "url": "https://api.github.com/repos/org/repo-name/issues/comments/1109524822/reactions"
        },
        "updated_at": "2022-04-26T08:47:20Z",
        "url": "https://api.github.com/repos/org/repo-name/issues/comments/1109524822",
        "user": {
          "avatar_url": "https://avatars.githubusercontent.com/u/13672022?v=4",
          "events_url": "https://api.github.com/users/user/events{/privacy}",
          "followers_url": "https://api.github.com/users/user/followers",
          "following_url": "https://api.github.com/users/user/following{/other_user}",
          "gists_url": "https://api.github.com/users/user/gists{/gist_id}",
          "gravatar_id": "",
          "html_url": "https://github.com/user",
          "id": 13672022,
          "login": "user",
          "node_id": "",
          "organizations_url": "https://api.github.com/users/user/orgs",
          "received_events_url": "https://api.github.com/users/user/received_events",
          "repos_url": "https://api.github.com/users/user/repos",
          "site_admin": false,
          "starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/user/subscriptions",
          "type": "User",
          "url": "https://api.github.com/users/user"
        }
      },
      "issue": {
        "active_lock_reason": null,
        "assignee": null,
        "assignees": [],
        "author_association": "CONTRIBUTOR",
        "body": "",
        "closed_at": null,
        "comments": 3,
        "comments_url": "https://api.github.com/repos/org/repo-name/issues/173/comments",
        "created_at": "2022-04-26T08:31:23Z",
        "draft": false,
        "events_url": "https://api.github.com/repos/org/repo-name/issues/173/events",
        "html_url": "https://github.com/org/repo-name/pull/173",
        "id": 0,
        "labels": [],
        "labels_url": "https://api.github.com/repos/org/repo-name/issues/173/labels{/name}",
        "locked": false,
        "milestone": null,
        "node_id": "",
        "number": 173,
        "performed_via_github_app": null,
        "pull_request": {
          "diff_url": "https://github.com/org/repo-name/pull/173.diff",
          "html_url": "https://github.com/org/repo-name/pull/173",
          "merged_at": null,
          "patch_url": "https://github.com/org/repo-name/pull/173.patch",
          "url": "https://api.github.com/repos/org/repo-name/pulls/173"
        },
        "reactions": {
          "+1": 0,
          "-1": 0,
          "confused": 0,
          "eyes": 0,
          "heart": 0,
          "hooray": 0,
          "laugh": 0,
          "rocket": 0,
          "total_count": 0,
          "url": "https://api.github.com/repos/org/repo-name/issues/173/reactions"
        },
        "repository_url": "https://api.github.com/repos/org/repo-name",
        "state": "open",
        "timeline_url": "https://api.github.com/repos/org/repo-name/issues/173/timeline",
        "title": "ci: dummy test",
        "updated_at": "2022-04-26T08:47:20Z",
        "url": "https://api.github.com/repos/org/repo-name/issues/173",
        "user": {
          "avatar_url": "https://avatars.githubusercontent.com/u/13672022?v=4",
          "events_url": "https://api.github.com/users/user/events{/privacy}",
          "followers_url": "https://api.github.com/users/user/followers",
          "following_url": "https://api.github.com/users/user/following{/other_user}",
          "gists_url": "https://api.github.com/users/user/gists{/gist_id}",
          "gravatar_id": "",
          "html_url": "https://github.com/user",
          "id": 0,
          "login": "user",
          "node_id": "",
          "organizations_url": "https://api.github.com/users/user/orgs",
          "received_events_url": "https://api.github.com/users/user/received_events",
          "repos_url": "https://api.github.com/users/user/repos",
          "site_admin": false,
          "starred_url": "https://api.github.com/users/user/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/user/subscriptions",
          "type": "User",
          "url": "https://api.github.com/users/user"
        }
      },
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.