Comment workflow setup
- Alpha
After the issue has been opened and any initial processing has been run, the comment workflow becomes the main driver of the rest of the process. This workflow is triggered by users commenting on issues and will do any further processing throughout the lifecycle of the issue.
The comment workflow should, at minimum, be triggered by creation of new
comments. The issue_comment
trigger also supports editing and deleting
comments, which may be useful for your use-case.
on:issue_comment:- created
The flexibility with the issue_comment
trigger lies in the comments
themselves. You can take a limitless number of actions based on user input!
With great power comes great responsibility!
Be careful to not make your workflow too complex. Otherwise, parsing comments becomes particularly challenging.
The issue_comment
trigger may seem misleading. This trigger applies to
comments on both issues and PRs. If you want to trigger workflows on comments
that are part of a PR review, use the pull_request_comment
trigger
instead.
Good IssueOps workflows make use of commands to trigger actions. These
commands are typically prefixed with a symbol, such as .
or /
, and are
descriptive of the action that will be taken when the command is processed. In
general, any workflow that involves processing comments should start by looking
for a specific command being run.
Keyword | Description |
---|---|
.submit | Submit a request for approval |
.approve | Approve a request |
.deny | Deny a request |
The examples you will see throughout this documentation make heavy use of the
github/command
action. This action makes
it easy to define your actions, who can run them, and what happens when they are
run.
The following code block shows a basic implementation of this action as part of
a IssueOps workflow to lint a pull request. In this example, the Lint Command
step will run any time a user comments with .lint
on a PR.
name: IssueOps Linteron:issue_comment:types:- createdjobs:lint:name: Lint Codebaseruns-on: ubuntu-latest# Only run on PR comments, not issue commentsif: ${{ github.event.pull_request }}# Minimum required permissions for the `github/command` actionpermissions:pull-requests: writeissues: writechecks: readsteps:- name: Lint Commandid: commanduses: github/command@vX.X.Xwith:command: .lint- if: ${{ steps.command.outputs.continue == 'true' }}name: Checkoutid: checkoutuses: actions/checkout@vX.X.X- if: ${{ steps.command.outputs.continue == 'true' }}name: Run Linterid: run-linterrun: npm run lint
As you can see, the Checkout
and Run Linter
steps only proceed if the
Lint Command
step's continue
output is 'true'
. The github/command
action
provides the continue
output to act as a gate for the rest of the workflow.
The high-level flow of the github/command
action is:
.lint
)Most comment workflows can be broken down into the following steps. Depending on your workflow, some of these may not be required.
Depending on your workflow, you may need users to be able to provide additional inputs in their comments. For example, you may want to allow users to specify a specific branch to lint, or a specific file to run tests on. Input arguments can also be passed into the comment body as part of a command.
The github/command
action can be extended to support input parameters.
Parameters must be added after the separator specified by the param_separator
argument (default: |
). Suppose you want to also allow users to provide a
branch name to run linting on. Users would enter a comment like this:
.lint | main
Within your workflow, you would need to parse the parameter value and use it to checkout the correct branch:
name: IssueOps Linteron:issue_comment:types:- createdjobs:lint:name: Lint Codebaseruns-on: ubuntu-latest# Only run on PR comments, not issue commentsif: ${{ github.event.pull_request }}# Minimum required permissions for the `github/command` actionpermissions:pull-requests: writeissues: writechecks: readsteps:- name: Lint Commandid: commanduses: github/command@vX.X.Xwith:command: .lintparam_separator: '|' # This is the default value- if: ${{ steps.command.outputs.continue == 'true' }}name: Checkoutid: checkoutuses: actions/checkout@vX.X.Xwith:ref: ${{ steps.command.outputs.params }}- if: ${{ steps.command.outputs.continue == 'true' }}name: Run Linterid: run-linterrun: npm run lint
The params
output is a string, not an array or object. If you need to pass
multiple parameters, you will need to include additional logic for parsing the
string.
Validate Early
Validate Often
Any time you interact with an issue, make sure to validate the body! Users can
edit the issue body at any time, so it's important to make sure you're working
with the latest information. The
issue-ops/parser
and
issue-ops/validator
actions can take
care of this for you. All you need to do is make sure they are included in your
comment processing workflows.
steps:- name: Parse Issue Bodyid: parseuses: issue-ops/parser@vX.X.Xwith:body: ${{ github.event.issue.body }}issue-form-template: my-issue-form.yml- name: Validate Issue Bodyid: validateuses: issue-ops/validator@vX.X.Xwith:issue-form-template: my-issue-form.ymlissue-number: ${{ github.event.issue.number }}parsed-issue-body: ${{ steps.parse.outputs.json }}
As an issue is processed, it will move through a series of states. The current state of an issue can be tracked via labels.
For example, suppose you have a workflow that requires users to submit their
request after is has been validated and confirmed. You could use the following
logic to check if the issue has already been submitted. That way, the processing
that would transition the issue from validated
to submitted
state does not
run again.
# This will only run if the request has already been submitted.if: contains(github.event.issue.labels.*.name, 'issueops:submitted') == truesteps:- name: Post Commentid: commentuses: peter-evans/create-or-update-comment@vX.X.Xwith:issue-number: ${{ github.event.issue.number }}body: |':clock1: It looks like this issue has already been submitted. Sit tight!'
Labels can be edited at any time! You want to consider checking if the state indicated by the issue labels matches the expected state of the issue.
By default, any user with read access to a repository can open issues and add
comments. If your IssueOps workflow involves certain authorized users being able
to perform actions (e.g. approving or denying requests), this can be a problem!
Instead, you should use the allowlist
option in the github/command
action to
restrict who has the ability to run certain commands.
For example, suppose you want to only allow certain administrators to approve
requests for new repositories. The following step would only allow @octocat and
@mona to run the .approve
command. Any other users who comment on the issue
with .approve
will not be able to push the request through to the next state.
- name: Approve Commandid: approveuses: github/command@vX.X.Xwith:command: .approveallowlist: octocat,mona
If you want to use teams to control access, you will also need to provide a
token with read:org
scope for the allowlist_pat
property. For information on
creating a GitHub App and using it to generate a token in a GitHub Actions
workflow, see GitHub App setup.
- uses: actions/create-github-app-token@vX.X.Xid: tokenwith:app_id: ${{ secrets.MY_GITHUB_APP_ID }}private_key: ${{ secrets.MY_GITHUB_APP_PEM }}owner: ${{ github.repository_owner }}- name: Approve Commandid: approveuses: github/command@vX.X.Xwith:command: .approveallowlist: octo-org/admin-teamallowlist_pat: ${{ steps.token.outputs.token }}
Alternatively, you can restrict access to specific roles in your IssueOps
repository using the permissions
property. By default, anyone with write,
maintain, or administrator access to the repository can run commands.
- name: Approve Commandid: approveuses: github/command@vX.X.Xwith:command: .approvepermissions: admin,maintain # Restrict users with write access
This is where the magic happens! You can use any series of actions to process
your request. For example, the
actions/github-script
action is a
great choice for interacting with GitHub APIs.
Make sure to gate these steps using the continue
output from the
github/command
action!
- if: ${{ steps.command.outputs.continue == 'true' }}name: Add User to Teamid: add-useruses: actions/github-script@vX.X.Xwith:token: ${{ steps.token.outputs.token }}script: |const request = JSON.parse('${{ steps.parse.outputs.json }}')await github.rest.teams.addOrUpdateMembershipForUserInOrg({org: context.repo.owner,team_slug: request.team_name,username: '${{ github.event.issue.user.login }}',role: 'member'})
Once any processing has been completed, the issue can be transitioned to the next state. This is done by adding or removing labels from the issue.
For example, suppose you have a workflow that requires users to submit their
request after is has been validated and confirmed. You can use the
issue-ops/labeler
action to transition
the issue from validated
to submitted
state.
- if: ${{ steps.command.outputs.continue == 'true' }}name: Set Submitted Stateid: set-submitteduses: issue-ops/labeler@vX.X.Xwith:action: addissue_number: ${{ github.event.issue.number }}labels: |issueops:submitted
Any time processing is done on an issue, its helpful to let users know:
The github/command
action takes care of the first part for you. It will
automatically add a reaction to any comments that contain a command.
- uses: github/command@vX.X.Xid: commandwith:command: .lintreaction: eyes
Any of the following reactions can be added:
Value | Emoji |
---|---|
+1 | 👍 |
-1 | 👎 |
laugh | 😄 |
confused | 😕 |
heart | ❤️ |
hooray | 🎉 |
rocket | 🚀 |
eyes | 👀 |
After the command has been received, adding comments to the issue to indicate work is being done is a great way to keep users informed. Once processing is complete, a comment can be added to the issue to indicate the outcome.
- if: ${{ steps.command.outputs.continue == 'true' }}name: Add Complete Commentid: comment-completeuses: peter-evans/create-or-update-comment@vX.X.Xwith:issue-number: ${{ github.event.issue.number }}body: |Your request has been processed! Here are the details of the request:- **Team:** ${{ steps.parse.outputs.json.team_name }}- **Role:** `member`