Automating with GitHub issue forms
Tags: github, githubactions, devops, cicd
Published: 2023-06-12 11:40:31

If you are familiar with [issue templates](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-templates), it is a good way to customise templates whenever anyone wants to create a new issue.

Issue templates need a **markdown file**, so when a user opens a new issue, it has a template in markdown, so the user needs to modify whatever he wants to have it in the same format.

![Issue template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7pqd4b0sh3gif808dhyr.png)

> _- What is the bad point about that?_
\+ A user can modify the template provided, change the headings, or completely remove everything and create a new issue.

A user that creates a new issue with this kind of template can manipulate the issue to change anything that is provided by the template.

## New issue forms

So, what are [issue forms](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#creating-issue-forms) for?
Issue forms are a new type of issue template. With issue forms, you can create customizable web form fields. So you have structured information on the issues you need.

> Issue forms are currently in beta.

You should have it in the same folder as the issue templates, but issue forms have a **YAML** extension. So, whenever a user needs to open a new issue, the user cannot change headings or completely remove everything, he won't be so evil.

![Issue form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7p1f0z8sux5pgmgdu4y.png)

Here we have a form that the user can fill out and select multiple values from, so the form will always be the same.

And this is totally **amazing**! Why? From here, it is so easy to automate anything you need.
So, let's say you need to run an script based on the inputs the user opening the form is providing. We can run a workflow from the [issue created event](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues), and the event body has the same format for every issue created; we can pick all the values provided and work with that.

Ok, but how? I created a repository called [IssueChatbotTemplate](https://github.com/DrDonoso/IssueChatbotTemplate) so you can copy the workflow.

Let's analyze it here:

``` yaml
---
name: issue
run-name: 
on:
    issues:
    types: [opened]

jobs:
    issue:
        name: Get inputs from issue form
        runs-on: ubuntu-latest
        if: ${{ contains(github.event.issue.title , '[Test Issue]')}}
        timeout-minutes: 30
        steps:
            - name: Add Comment
            uses: actions/github-script@v6
            with:
                script: |
                github.rest.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: 'Workflow running on: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}',
                });
            
            - name: Get inputs
            id: inputs
            shell: bash
            run: |
                add_output(){
                local k=$1
                local v=$2
                echo "$k=$v" >> $GITHUB_OUTPUT
                echo "✅ Output generated with key: $k, value: $v"
                }
                process_key(){
                    local process_key=$1
                    # Trim leading and trailing whitespace
                    process_key="$(echo -e "${process_key}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"

                    # Substitute spaces with underscores
                    process_key="${process_key// /_}"

                    # Convert to lowercase
                    process_key="${process_key,,}"

                    echo "$process_key";
                }

                process_checkbox_line(){
                    local checkbox_line=$1
                    option=$(echo "$checkbox_line" | sed 's/- \[[^]]*\] //')
                    
                    key="$(process_key "$option")"
                    value=$(echo "$checkbox_line" | grep -qE -- '- \[X\]' && echo "true" || echo "false")

                    add_output "$key" "$value"

                    escaped_lookfor=$(echo "$checkbox_line" | sed 's/[][()\.^$?*+{}|]/\\&/g')
                    nextcheckbox=$(echo "${{ github.event.issue.body }}" | awk "/$escaped_lookfor$/{getline; print}")

                    if [[ "$nextcheckbox" == "- ["* && -n "$nextcheckbox" && "$nextcheckbox" != "$checkbox_line" ]]; then
                        process_checkbox_line "$nextcheckbox"
                    fi
                }

                echo "${{ github.event.issue.body }}" | awk '/^###/ { print }' | while read line; do
                    echo "🔍 Processing line: $line"
                    value=$(echo "${{ github.event.issue.body }}" | awk "/^$line$/{getline;getline; print}")

                    if [[ "$value" == "- ["* ]]; then
                        # Processing a checkbox
                        echo "☑️ Detected a checkbox!"
                        process_checkbox_line "$value"
                    else
                        # Remove "###" from the beginning of the string
                        key=${line##"###"}
                        
                        key="$(process_key "$key")"
                        add_output "$key" "$value"
                    fi
                done


            - uses: actions/github-script@v6
            name: Add label
            with:
                script: |
                github.rest.issues.addLabels({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    labels: ["environment/${{steps.inputs.outputs.dropdown_label}}"]
                });

        outputs:
            input: ${{ steps.inputs.outputs.input_label }}
            dropdown: ${{ steps.inputs.outputs.dropdown_label }}
            dropdown_multiple: ${{ steps.inputs.outputs.dropdown_multiple_label }}
            textarea: ${{ steps.inputs.outputs.textarea_label }}
            checkbox1: ${{ steps.inputs.outputs.checkbox_label_1 }}
            checkbox2: ${{ steps.inputs.outputs.checkbox_label_2 }}

    process:
        name: Process ${{needs.issue.outputs.dropdown}}
        runs-on: ubuntu-latest
        needs: issue
        steps:
        - name: Add Comment
            uses: actions/github-script@v6
            with:
            script: |
                const body = `### Processed outputs:

                **input_label**: ${{ needs.issue.outputs.input }}
                **dropdown_label**: ${{ needs.issue.outputs.dropdown }}
                **dropdown_multiple_label**: ${{ needs.issue.outputs.dropdown_multiple }}
                **textarea_label**: ${{ needs.issue.outputs.textarea }}
                **checkbox_label_1**: ${{ needs.issue.outputs.checkbox1 }}
                **checkbox_label_2**: ${{ needs.issue.outputs.checkbox2 }}
                `;

                github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: body
                });
```

Here I'm running the workflow whenever a new issue is created; if the issue has the title [Test Issue] I run the first job (*Yes, the user can remove the [Test Issue] title, you can manage it with labels, for example. And yes, the user can remove the label also.*).
Then, in the first step, I'm adding a comment to the same issue, so the user knows the workflow is running.
The next step is where magic happens: grabbing all inputs and adding them as GitHub outputs with the label formatted and the value.
Also, I'm adding some labels to the issue based on the input provided, so for example, you can know which environment is faster. Adding outputs for this operation is the final step in the job. That's where the work should end.

And finally, in a new job, adding a comment with the id and the value known in the same issue (*this is something you probably won't need, just for debugging purposes*).

So, as a result, we will have something like that: 

![Issue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kb1yywdkyiq6zcnj96eh.png)

And this is just a start point, from here you can grow it by calling another job to deploy the application to some environment, running an automation to grant some permissions for some user (*For security changes, you will need to check the user creating the issue, of course*), or any other idea you want to automate.
Also, you can improve it. So let's say you have like 3-4 issue forms in the new template, so you have 3-4 workflows for these issue forms.
Whenever you open a new issue, all workflows will be executed, but not the jobs. So you can improve it by changing the event of these workflows to workflow_call and having 1 main workflow that adds the first comment in the issue, and then with a strategy calling the workflow you want to execute based on title, labels, etc...

Thanks for your time reading this! I highly appreciate any feedback on any of my social media accounts or here.