Submit

Tell GitHub Actions the issue is ready for processing.
  • Alpha

Overview

Once your issue has been parsed and validated, it's ready for processing! At this point, processing can mean a lot of things and is entirely dependent on your use case. For example, if you're using IssueOps to access administrative functions, you may require a human to review and approve the issue. Or, if you're using IssueOps to track PTO requests, you may not need any additional approvals and can simply mark the issue as processed.

This page walks through the process of submitting a request after it has been validated. In particular, it covers requesting approval from authorized users or teams.

Wouldn't opening the issue count as the act of submitting it?

Absolutely! However, the act of opening an issue may not be the best indicator that an issue is in the Submitted state in your workflow. What if you need to do additional processing on the validated request which requires confirmation from the user?

Using the new repository request as an example, your organization may want to enforce certain naming conventions for repositories, such as prefixing the name with the user's department. In this case, when a user opens a request and asks for a repository named pto-requests, you could have them confirm that the generated name of hr-pto-requests is acceptable before submitting the request for further processing.

Command actions

This is where the github/command action comes into play. This action allows you to specify the who, what, when, and where of activities that can be performed on an issue. For example, if you request approval for a new repository, the github/command action ensures that any user cannot approve the request. Instead, only users or teams you specify can.

As with other actions that call GitHub APIs, if you want to include GitHub teams in the allowlist feature, you must provide a valid token in the allowlist_pat input. This can be a token generated from a GitHub App!

steps:
- name: Approve Command
id: approve
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .approve

This step acts as the gate for any further processing of the issue. The continue output can be used to conditionally invoke further steps. For example, if the continue output is 'true', the user who commented on the issue with .approve was indeed authorized to approve the request.

steps:
- name: Approve Command
id: approve
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .approve
##############################################
# This is a great time to re-run validation! #
##############################################
- if: ${{ steps.approve.outputs.continue == 'true' }}
run: echo "This request is approved!"

With any approval workflow, you should also consider what happens when a request is explicitly denied This is easy to implement as a separate github/command step that looks for the .deny command. As with the approval command, if the user who commented on the issue is authorized to deny requests, the continue output would be 'true'.

steps:
- name: Approve Command
id: approve
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .approve
- name: Deny Command
id: deny
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .deny
- if: ${{ steps.approve.outputs.continue == 'true' }}
run: echo "This request is approved :)"
- if: ${{ steps.deny.outputs.continue == 'true' }}
run: echo "This request is denied :("

New repository request

Up until this point, everything has been handled as part of the issue creation workflow. Now that the issue has been validated, any further processing is done via comments, labels, reactions, and so on.

Create the comment workflow file

The first step is to create a workflow file that will be triggered when a user comments on an issue. This workflow file will be responsible for parsing the comment and determining the following:

  • The comment is on an issue that is part of our IssueOps workflow
  • The comment is a command word
  • The user is authorized to call that command

In this example, we will set up two different jobs that will run when the request is approved or denied.

name: Issue Comment
# This workflow runs any time a comment is added to an issue. The comment body
# is read and used to determine what action to take.
on:
issue_comment:
types:
- created
jobs:
# This job handles the case where a user comments with `.approve`.
approve:
name: Approve Request
runs-on: ubuntu-latest
steps:
- name: Approve Command
id: approve
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .approve
- if: ${{ steps.approve.outputs.continue == 'true' }}
run: echo "This request is approved!"
# This job handles the case where a user comments with `.deny`.
deny:
name: Deny Request
runs-on: ubuntu-latest
steps:
- name: Deny Command
id: deny
uses: github/command@vX.X.X
with:
allowed_contexts: issue
allowlist: octo-org/approvers
allowlist_pat: ${{ secrets.MY_TOKEN }}
command: .deny
- if: ${{ steps.deny.outputs.continue == 'true' }}
run: echo "This request is denied!"

Trigger the workflow

In the above workflow, both the approve and deny jobs are triggered when a user comments on an issue or PR. Though the github/command actions will act as one gate, you may want to add additional conditions to ensure that the workflow is not run when the issue is in a state that does not require approval. For example, this workflow doesn't need to run if:

  • The issue is not part of this IssueOps workflow
  • The request is not in the Submitted state
  • The request is already approved

Workflow conditions can be used to control when the workflow jobs are invoked.

It's always a good idea to consider negative cases when writing workflows. What would happen if someone comments .approve but the request has already been approved? Adding a comment stating the request has already been approved helps prevent confusion. Communication is key!

name: Issue Comment
on:
issue_comment:
types:
- created
jobs:
approve:
name: Approve Request
runs-on: ubuntu-latest
# Only run when the following conditions are true:
# - The issue has the `issueops:new-repository` label
# - The issue has the `issueops:validated` label
# - The issue does not have the `issueops:approved` label
# - The issue is open
if: |
contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
contains(github.event.issue.labels.*.name, 'issueops:validated') &&
contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
github.event.issue.state == 'open'
steps:
# ...
deny:
name: Deny Request
runs-on: ubuntu-latest
# Only run when the following conditions are true:
# - The issue has the `issueops:new-repository` label
# - The issue has the `issueops:validated` label
# - The issue does not have the `issueops:approved` label
# - The issue is open
if: |
contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
contains(github.event.issue.labels.*.name, 'issueops:validated') &&
contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
github.event.issue.state == 'open'
steps:
# ...

This seems like duplication of the same checks. Plus, we haven't followed our own rule: Validate Early. Validate Often. Instead, lets move this to a separate job that re-runs validation checks.

name: Issue Comment
on:
issue_comment:
types:
- created
jobs:
validate:
name: Validate Request
runs-on: ubuntu-latest
# Only run when the following conditions are true:
# - The issue has the `issueops:new-repository` label
# - The issue has the `issueops:validated` label
# - The issue does not have the `issueops:approved` label
# - The issue is open
if: |
contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
contains(github.event.issue.labels.*.name, 'issueops:validated') &&
contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
github.event.issue.state == 'open'
permissions:
contents: read
id-token: write
issues: write
outputs:
request: ${{ steps.parse.outputs.request }}
steps:
- name: Remove Labels
id: remove-label
uses: issue-ops/labeler@vX.X.X
with:
action: remove
issue_number: ${{ github.event.issue.number }}
labels: |
issueops:validated
issueops:submitted
- name: Get App Token
id: token
uses: actions/create-github-app-token@vX.X.X
with:
app_id: ${{ secrets.MY_GITHUB_APP_ID }}
private_key: ${{ secrets.MY_GITHUB_APP_PEM }}
owner: ${{ github.repository_owner }}
- name: Checkout
id: checkout
uses: actions/checkout@vX.X.X
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@vX.X.X
with:
node-version-file: .node-version
cache: npm
- name: Install Packages
id: npm
run: npm ci
- name: Parse Issue
id: parse
uses: issue-ops/parser@vX.X.X
with:
body: ${{ github.event.issue.body }}
issue-form-template: new-repository-request.yml
- name: Validate Issue
id: validate
uses: issue-ops/validator@vX.X.X
with:
issue-form-template: new-repository-request.yml
github-token: ${{ steps.token.outputs.token }}
parsed-issue-body: ${{ steps.parse.outputs.json }}
- if: ${{ steps.validate.outputs.result == 'success' }}
name: Add Validated Label
id: add-label
uses: issue-ops/labeler@vX.X.X
with:
action: add
issue_number: ${{ github.event.issue.number }}
labels: |
issueops:validated
approve:
name: Approve Request
runs-on: ubuntu-latest
# Only run after validation has completed.
needs: validate
steps:
# ...
deny:
name: Deny Request
runs-on: ubuntu-latest
# Only run after validation has completed.
needs: validate
steps:
# ...

With this workflow, we know that the request has been validated before we handle any approval or denial. This is a good example of Validate Early. Validate Often.

Next steps

Depending on if the request is approved or denied, you may want to take further actions. For example, if the request is approved, you could create the repository, add a comment to the issue, and close it as completed. On the other hand, if the request is denied, you could close the issue as not planned.

Continue to the approve or deny sections to learn more.