Submit

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.
1steps:
2  - name: Approve Command
3    id: approve
4    uses: github/command@vX.X.X
5    with:
6      allowed_contexts: issue
7      allowlist: octo-org/approvers
8      allowlist_pat: ${{ secrets.MY_TOKEN }}
9      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.
1steps:
2  - name: Approve Command
3    id: approve
4    uses: github/command@vX.X.X
5    with:
6      allowed_contexts: issue
7      allowlist: octo-org/approvers
8      allowlist_pat: ${{ secrets.MY_TOKEN }}
9      command: .approve
10
11  ##############################################
12  # This is a great time to re-run validation! #
13  ##############################################
14
15  - if: ${{ steps.approve.outputs.continue == 'true' }}
16    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'.
1steps:
2  - name: Approve Command
3    id: approve
4    uses: github/command@vX.X.X
5    with:
6      allowed_contexts: issue
7      allowlist: octo-org/approvers
8      allowlist_pat: ${{ secrets.MY_TOKEN }}
9      command: .approve
10
11  - name: Deny Command
12    id: deny
13    uses: github/command@vX.X.X
14    with:
15      allowed_contexts: issue
16      allowlist: octo-org/approvers
17      allowlist_pat: ${{ secrets.MY_TOKEN }}
18      command: .deny
19
20  - if: ${{ steps.approve.outputs.continue == 'true' }}
21    run: echo "This request is approved :)"
22
23  - if: ${{ steps.deny.outputs.continue == 'true' }}
24    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.
1name: Issue Comment
2
3# This workflow runs any time a comment is added to an issue. The comment body
4# is read and used to determine what action to take.
5on:
6  issue_comment:
7    types:
8      - created
9
10jobs:
11  # This job handles the case where a user comments with `.approve`.
12  approve:
13    name: Approve Request
14    runs-on: ubuntu-latest
15
16    steps:
17      - name: Approve Command
18        id: approve
19        uses: github/command@vX.X.X
20        with:
21          allowed_contexts: issue
22          allowlist: octo-org/approvers
23          allowlist_pat: ${{ secrets.MY_TOKEN }}
24          command: .approve
25
26      - if: ${{ steps.approve.outputs.continue == 'true' }}
27        run: echo "This request is approved!"
28
29  # This job handles the case where a user comments with `.deny`.
30  deny:
31    name: Deny Request
32    runs-on: ubuntu-latest
33
34    steps:
35      - name: Deny Command
36        id: deny
37        uses: github/command@vX.X.X
38        with:
39          allowed_contexts: issue
40          allowlist: octo-org/approvers
41          allowlist_pat: ${{ secrets.MY_TOKEN }}
42          command: .deny
43
44      - if: ${{ steps.deny.outputs.continue == 'true' }}
45        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.
1name: Issue Comment
2
3on:
4  issue_comment:
5    types:
6      - created
7
8jobs:
9  approve:
10    name: Approve Request
11    runs-on: ubuntu-latest
12
13    # Only run when the following conditions are true:
14    #   - The issue has the `issueops:new-repository` label
15    #   - The issue has the `issueops:validated` label
16    #   - The issue does not have the `issueops:approved` label
17    #   - The issue is open
18    if: |
19      contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
20      contains(github.event.issue.labels.*.name, 'issueops:validated') &&
21      contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
22      github.event.issue.state == 'open'
23
24    steps:
25      # ...
26
27  deny:
28    name: Deny Request
29    runs-on: ubuntu-latest
30
31    # Only run when the following conditions are true:
32    #   - The issue has the `issueops:new-repository` label
33    #   - The issue has the `issueops:validated` label
34    #   - The issue does not have the `issueops:approved` label
35    #   - The issue is open
36    if: |
37      contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
38      contains(github.event.issue.labels.*.name, 'issueops:validated') &&
39      contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
40      github.event.issue.state == 'open'
41
42    steps:
43      # ...
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.
1name: Issue Comment
2
3on:
4  issue_comment:
5    types:
6      - created
7
8jobs:
9  validate:
10    name: Validate Request
11    runs-on: ubuntu-latest
12
13    # Only run when the following conditions are true:
14    #   - The issue has the `issueops:new-repository` label
15    #   - The issue has the `issueops:validated` label
16    #   - The issue does not have the `issueops:approved` label
17    #   - The issue is open
18    if: |
19      contains(github.event.issue.labels.*.name, 'issueops:new-repository') &&
20      contains(github.event.issue.labels.*.name, 'issueops:validated') &&
21      contains(github.event.issue.labels.*.name, 'issueops:approved') == false &&
22      github.event.issue.state == 'open'
23
24    permissions:
25      contents: read
26      id-token: write
27      issues: write
28
29    outputs:
30      request: ${{ steps.parse.outputs.request }}
31
32    steps:
33      - name: Remove Labels
34        id: remove-label
35        uses: issue-ops/labeler@vX.X.X
36        with:
37          action: remove
38          issue_number: ${{ github.event.issue.number }}
39          labels: |
40            issueops:validated
41            issueops:submitted
42
43      - name: Get App Token
44        id: token
45        uses: actions/create-github-app-token@vX.X.X
46        with:
47          app_id: ${{ secrets.MY_GITHUB_APP_ID }}
48          private_key: ${{ secrets.MY_GITHUB_APP_PEM }}
49          owner: ${{ github.repository_owner }}
50
51      - name: Checkout
52        id: checkout
53        uses: actions/checkout@vX.X.X
54
55      - name: Setup Node.js
56        id: setup-node
57        uses: actions/setup-node@vX.X.X
58        with:
59          node-version-file: .node-version
60          cache: npm
61
62      - name: Install Packages
63        id: npm
64        run: npm ci
65
66      - name: Parse Issue
67        id: parse
68        uses: issue-ops/parser@vX.X.X
69        with:
70          body: ${{ github.event.issue.body }}
71          issue-form-template: new-repository-request.yml
72
73      - name: Validate Issue
74        id: validate
75        uses: issue-ops/validator@vX.X.X
76        with:
77          issue-form-template: new-repository-request.yml
78          github-token: ${{ steps.token.outputs.token }}
79          parsed-issue-body: ${{ steps.parse.outputs.json }}
80
81      - if: ${{ steps.validate.outputs.result == 'success' }}
82        name: Add Validated Label
83        id: add-label
84        uses: issue-ops/labeler@vX.X.X
85        with:
86          action: add
87          issue_number: ${{ github.event.issue.number }}
88          labels: |
89            issueops:validated
90
91  approve:
92    name: Approve Request
93    runs-on: ubuntu-latest
94
95    # Only run after validation has completed.
96    needs: validate
97
98    steps:
99      # ...
100
101  deny:
102    name: Deny Request
103    runs-on: ubuntu-latest
104
105    # Only run after validation has completed.
106    needs: validate
107
108    steps:
109      # ...
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.