Hackathon Automation with GitHub Actions, Microsoft 365 and Power Platform
Published Aug 27 2021 06:17 AM 1,775 Views

Generally speaking, an off-line hackathon event takes place with people getting together at the same time and place for about two to three nights, intensively. On the other hand, all events have turned into online-only nowadays, and there's no exception for the hackathon events either. To keep the same event experiences, hackathon organisers use many online collaboration tools. In this case, almost the same number of event staff members are necessary. What if you have limited resources and budget and are required to run the online hackathon event?


For two weeks, I recently ran an online-only hackathon event called HackaLearn from August 2, 2021. This post is the retrospective of the event from the event organiser's perspective. If anyone is planning a hackathon with a similar concept, I hope this post could be helpful.


The Background


In May 2021 at //Build Conference, Azure Static Web Apps (ASWA) became generally available. It's relatively newer than the other competitors' ones meaning it is less popular than the others. This HackaLearn event is one of the practices to promote ASWA. The idea was simple. We're not only running a hackathon event but also offering the participants learning experiences with Microsoft Learn to participants so that they can feel how convenient ASWA is to use. Therefore, all participants can learn ASWA and build their app with ASWA – this was the direction.


HackaLearn Banner


In fact, the first HackaLearn event was held in Israel, and other countries in the EMEA region have been running this event. I also borrowed the concept and localised the format for Korean uni students. With support from Microsoft Learn Student Ambassadors (MLSA) and GitHub Campus Experts (GCE), they review the participants pull requests and external field experts were invited as mentors and ran online mentoring sessions.


The Problems


As mentioned above, running a hackathon event requires intensive, dedicated and exclusive resources, including time, people and money. However, none of them was adequate. I've got very limited resources, and even I couldn't dedicate myself to this event either. I was the only one who could operate the event. Both MLSAs and GCEs were dedicated for PR reviews and mentors for mentoring sessions. Automating all the event operation processes was the only answer for me.


How can I automate all the things?


For me, finding out the solution is the key focus area throughout this event.


The Constraints


There were a few constraints to be considered.


  • No Website for Hackathon

    :police_car_light: There was no website for HackaLearn. Usually, the event website is built on a one-off basis, which seems less economical.
    :backhand_index_pointing_right: Therefore, I decided to use the GitHub repository for the event because it offers many built-in features such as Project, Discussions, Issues, Wiki, etc.

  • No Place for Participant Registration

    :police_car_light: There was no registration form.
    :backhand_index_pointing_right: Therefore, I decided to use Microsoft Forms.

  • No Database for Participant Management

    :police_car_light: There was no database for the participant management to record their challenge progress.
    :backhand_index_pointing_right: Therefore, instead of provisioning a database instance, I decided to use Microsoft Lists.

  • No Dashboard for Teams and Individuals Progress Tracking

    :police_car_light: There was no dashboard to track each team's and each participant's progress.
    :backhand_index_pointing_right: So instead, I decided to use their team page by merging their pull requests.


I've defined the overall business process workflow in the following sequence diagrams. All I needed was to sort out those limitations stated above. To me, it was Power Platform, GitHub Actions and Microsoft 365 with minimal coding efforts and maximum outcomes.


The Plans for Process Automation


All of sudden, the limitations above have become opportunities to experiment with the new process automation!



So, the GitHub repository and Microsoft 365 services are fully integrated with GitHub Actions workflows and Power Automate workflows. As a result, I was able to save a massive amount of time and money with them.


The Result – Participant Registration


The first automation process I worked on was about storing data. The participant details need to be saved in Microsoft Lists. When a participant enters their details through Microsoft Forms, then a Power Automate workflow is triggered to process the registration details. At the same time, the workflow calls a GitHub Actions workflow to create a team page for the participant. Here's the simple sequence diagram describing this process.


Registration Sequence Diagram


The overall process is divided into two parts – one to process participant details in the Power Automate workflow, and the other to process the details in the GitHub Actions workflow.


Power Automate Workflow


Let's have a look at the Power Automate part. When a participant registers through Microsoft Forms, the form automatically triggers a Power Automate workflow. The workflow checks the email address whether the participant has already registered or not. If the email doesn't exist, the participant details are stored to Microsoft Lists.


Registration Flow 1


Then it generates a team page. Instead of creating it directly from the Power Automate workflow, it builds the page content and sends it to the GitHub Actions workflow. The workflow_dispatch event is triggered for this action.


Registration Flow 2


Finally, the workflow sends a confirmation email. In terms of the name, participants may register themselves with English names or Korean names. Therefore, I need logic to check the participant's name. If the participant name is written in English, it should be [Given Name] [Surname] (with a space; eg. Justin Yoo). If it's written in Korean, it should be [Surname][Given Name] (without a space; eg. 유저스틴). The red-boxed actions are responsible for identifying the participant's name. It may be simplified by adopting a custom connector with an Azure Functions endpoint.


Registration Flow 3


GitHub Actions Workflow


As mentioned above, the Power Automate workflow calls a GitHub Actions workflow to generate a team page. Let's have a look. The workflow_dispatch event takes the input details from Power Automate, and they are teamName and content.


name: On Team Page Requested

        description: The name of team
        required: true
        default: Team_HackaLearn
        description: The content of the file to be created
        required: true
        default: Hello HackaLearn


Since GitHub Marketplace has various types of Actions, I can simply choose one to create the team page, commit the change and push it back to the repository.


    - name: Create team page
      uses: DamianReeves/write-file-action@master
        path: "./teams/${{ github.event.inputs.teamName }}.md"
        contents: ${{ github.event.inputs.content }}
        write-mode: overwrite
    - name: Commit team page
      shell: bash
      run: |
        git config --local user.email "hackalearn.korea@outlook.com"
        git config --local user.name "HackaLearn Korea"
        git add ./teams/\* --force
        git commit -m "Team: ${{ github.event.inputs.teamName }} added"
    - name: Push team page
      uses: ad-m/github-push-action@master
        github_token: ${{ secrets.GITHUB_TOKEN }}
        branch: ${{ github.ref }}


Now, I've got the registration process fully automated. Let's move on.


The Result – Challenges Update


In this HackaLearn event, each participant was required to complete six challenges. Every time they finish one challenge, they MUST update their team page and create a PR to reflect their progress. As there are not many differences between the challenges, I'm going to use the Social Media Challenge as an example.


Here's the simple sequence diagram describing the process.


Social Media Challenge Sequence Diagram


  1. After the participant posts a post to their social media, they update their team page and raise a PR. Then, a GitHub Actions workflow labels the PR with review-required and assigns a reviewer.
  2. The assigned reviewer checks the social media post whether it's appropriately hashtagged with #hackalearn and #hackalearnkorea.
  3. Once confirmed, the reviewer adds the review-completed label to the PR. Then another GitHub Actions workflow automatically removes the review-required label from the PR.
  4. The reviewer completes the review by leaving a comment of /socialsignoff, and the comment triggers another GitHub Actions workflow. The workflow calls the Power Automate workflow that updates the record on Microsoft Lists with the challenge progress.
  5. The Power Automate workflow calls back to another GitHub Actions workflow to add record-updated and completed-social labels to the PR and remove the review-completed labels from it.
  6. If there is an issue while updating the record, the GitHub Actions workflow adds the review-required label so that the assigned reviewer starts review again.


GitHub Actions Workflow


As described above, there are five GitHub Actions workflow used to handle this request.


Challenge Update PR


The GitHub Actions workflow is triggered by the participant requesting a new PR. The event triggered is pull_request_target, and it's only activated when the changes occur under the teams directory.


name: On Challenge Submitted

    - opened
    - main
    - 'teams/**/*.md'


If the PR is created later than the due date and time, the PR should not be accepted. Therefore, A PowerShell script is used to check the due date automatically. Since the PR's created_at value is the UTC value, it should be converted to the Korean local time, included in the PowerShell script.


    name: 'Add a label on submission: review-required'

    runs-on: ubuntu-latest

    - name: Get PR date/time
      id: checkpoint
      shell: pwsh
      run: |
        $tz = [TimeZoneInfo]::FindSystemTimeZoneById("Asia/Seoul")
        $dateSubmitted = [DateTimeOffset]::Parse("${{ github.event.pull_request.created_at }}")
        $offset = $tz.GetUtcOffset($dateSubmitted)

        $dateSubmitted = $dateSubmitted.ToOffset($offset)
        $dateDue = $([DateTimeOffset]::Parse("2021-08-16T00:00:00.000+09:00"))
        $isOverdue = "$($dateSubmitted -gt $dateDue)".ToLowerInvariant()

        $dateSubmittedValue = $dateSubmitted.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz")
        $dateDueValue = $dateDue.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz")

        echo "::set-output name=dateSubmitted::$dateSubmittedValue"
        echo "::set-output name=dateDue::$dateDueValue"
        echo "::set-output name=isOverdue::$isOverdue"


If the PR is over the due, it's immediately rejected and closed.


    - name: Add a label - Overdue
      if: ${{ steps.checkpoint.outputs.isOverdue == 'true' }}
      uses: buildsville/add-remove-label@v1
        token: "${{ secrets.GITHUB_TOKEN }}"
        label: 'OVERDUE-SUBMIT'
        type: add
    - name: Comment to PR - Overdue
      if: ${{ steps.checkpoint.outputs.isOverdue == 'true' }}
      uses: bubkoo/auto-comment@v1
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        pullRequestOpened: |
          👋🏼 @{{ author }} 님!
          * PR 제출 시각: ${{ steps.checkpoint.outputs.dateSubmitted }}
          * PR 마감 시각: ${{ steps.checkpoint.outputs.dateDue }}
          안타깝게도 제출하신 PR은 마감 기한인 ${{ steps.checkpoint.outputs.dateDue }}을 넘기셨습니다. 😭 따라서, 이번 HackaLearn 이벤트에 반영되지 않습니다.
          그동안 HackaLearn 이벤트에 참여해 주셔서 감사 드립니다. 다음 기회에 다시 만나요!
    - name: Close PR - Overdue
      if: ${{ steps.checkpoint.outputs.isOverdue == 'true' }}
      uses: superbrothers/close-pull-request@v3
        comment: "제출 기한 종료"


If it's before the due, label the PR, leave a comment and randomly assign a reviewer.


    - name: Add a label
      if: ${{ steps.checkpoint.outputs.isOverdue == 'false' }}
      uses: actions/labeler@v3
        repo-token: "${{ secrets.GITHUB_TOKEN }}"
        configuration-path: '.github/labeler.yml'
    - name: Comment to PR
      if: ${{ steps.checkpoint.outputs.isOverdue == 'false' }}
      uses: bubkoo/auto-comment@v1
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        pullRequestOpenedReactions: 'rocket, +1'
        pullRequestOpened: >
          👋🏼 @{{ author }} 님!

          챌린지 완료 PR를 생성해 주셔서 감사합니다! 🎉 참가자님의 해커톤 완주를 응원해요! 💪🏼

          PR 템플릿 작성 가이드라인을 잘 준수하셨는지 확인해주세요. 최대한 빠르게 리뷰하겠습니다! 😊

          🔹 From. HackaLearn 운영진 일동 🔹

          - name: Randomly assign a staff
      if: ${{ steps.checkpoint.outputs.isOverdue == 'false' }}
      uses: gerardabello/auto-assign@v1.0.1
        github-token: "${{ secrets.GITHUB_TOKEN }}"
        number-of-assignees: 1
        assignee-pool: "${{ secrets.PR_REVIEWERS }}"


Challenge Review Completed


The assigned reviewer confirms the challenge and labels the result. This labelling action triggers the following GitHub Actions workflow.


name: On Challenge Labelled

    - labeled
    - unlabeled

    name: 'Update a label'

    runs-on: ubuntu-latest

    - name: Respond to label
      uses: dessant/label-actions@v2
        process-only: prs


Challenge Review Approval


Commenting like /socialsignoff for the social media post challenge automatically triggers the following GitHub Actions workflow, with the event of issue_comment.


name: On Challenge Review Commented

    - created


The first step of this workflow is to check whether the commenter is the assigned reviewer, then find out which challenge is approved. The review-completed label MUST exist on the PR, and the commenter MUST be in the reviewer list (secrets.PR_REVIEWERS).



    if: ${{ github.event.issue.pull_request }}
    name: 'Sign-off challenge'

    runs-on: ubuntu-latest

    - name: Get checkpoints
      id: checkpoint
      shell: pwsh
      run: |
        $hasValidLabel = "${{ contains(github.event.issue.labels.*.name, 'review-completed') }}"
        $isCommenterAssignee = "${{ github.event.comment.user.login == github.event.issue.assignee.login }}"
        $isValidCommenter = "${{ contains(env.PR_REVIEWERS, github.event.comment.user.login) }}"
        $isAswaSignoff = "${{ github.event.comment.body == '/aswasignoff' }}"
        $isGhaSignoff = "${{ github.event.comment.body == '/ghasignoff' }}"
        $isSocialSignoff = "${{ github.event.comment.body == '/socialsignoff' }}"
        $isAppSignoff = "${{ github.event.comment.body == '/appsignoff' }}"
        $isRepoSignoff = "${{ github.event.comment.body == '/reposignoff' }}"
        $isRetroSignoff = "${{ github.event.comment.body == '/retrosignoff' }}"
        $timestamp = "${{ github.event.comment.created_at }}"
        echo "::set-output name=hasValidLabel::$hasValidLabel"
        echo "::set-output name=isCommenterAssignee::$isCommenterAssignee"
        echo "::set-output name=isValidCommenter::$isValidCommenter"
        echo "::set-output name=isAswaSignoff::$isAswaSignoff"
        echo "::set-output name=isGhaSignoff::$isGhaSignoff"
        echo "::set-output name=isSocialSignoff::$isSocialSignoff"
        echo "::set-output name=isAppSignoff::$isAppSignoff"
        echo "::set-output name=isRepoSignoff::$isRepoSignoff"
        echo "::set-output name=isRetroSignoff::$isRetroSignoff"
        echo "::set-output name=timestamp::$timestamp"


If all conditions are met, the workflow takes one action based on the type of the challenge. Each action calls a Power Automate workflow to update the record on Microsoft Lists, send a confirmation email, and calls back to another GitHub Actions workflow.


- name: Record challenge ASWA
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isAswaSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "aswa", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'

- name: Record challenge GHA
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isGhaSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "gha", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'

- name: Record challenge SOCIAL
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isSocialSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "social", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'

- name: Record challenge APP
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isAppSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "app", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'

- name: Record challenge REPO
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isRepoSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "repo", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'

- name: Record challenge RETRO
  if: ${{ steps.checkpoint.outputs.hasValidLabel == 'true' && steps.checkpoint.outputs.isCommenterAssignee == 'true' && steps.checkpoint.outputs.isValidCommenter == 'true' && steps.checkpoint.outputs.isRetroSignoff == 'true' }}
  uses: joelwmale/webhook-action@2.1.0
    url: ${{ secrets.FLOW_URL }}
    body: '{"gitHubId": "${{ github.event.issue.user.login }}", "challengeType": "retro", "timestamp": "${{ steps.checkpoint.outputs.timestamp }}", "prId": ${{ github.event.issue.number }} }'


Challenge Complete or Further Review


This GitHub Actions workflow completes the challenge, triggered by a Power Automate workflow through the workflow_dispatch event. Power Automate sends values of prId, labelsToAdd, labelsToRemove and isMergeable.


name: On Challenge Completed

        description: PR ID
        required: true
        default: ''
        description: The comma delimited labels to add
        required: true
        default: record-updated
        description: The comma delimited labels to remove
        required: true
        default: review-completed
        description: The value indicating whether the challenge is mergeable or not.
        required: true
        default: 'false'


The first action is to add labels to the PR and remove labels from the PR.


    name: 'Update labels'

    runs-on: ubuntu-latest

    - name: Update labels on PR
      shell: pwsh
      run: |
        $headers = @{ "Authorization" = "token ${{ secrets.GITHUB_TOKEN }}"; "User-Agent" = "HackaLearn Bot"; "Accept" = "application/vnd.github.v3+json" }

        $owner = "devrel-kr"
        $repository = "HackaLearn"
        $issueId = "${{ github.event.inputs.prId }}"

        $labelsToAdd = "${{ github.event.inputs.labelsToAdd }}" -split ","
        $body = @{ "labels" = $labelsToAdd }

        $url = "https://api.github.com/repos/$owner/$repository/issues/$issueId/labels"
        Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $($body | ConvertTo-Json)

        $labelsToRemove = "${{ github.event.inputs.labelsToRemove }}" -split ","
        $labelsToRemove | ForEach-Object {
          $label = $_;
          $url = "https://api.github.com/repos/$owner/$repository/issues/$issueId/labels/$label";
          Invoke-RestMethod -Method Delete -Uri $url -Headers $headers


And finally, this action merges the PR. If there's an error on the Power Automate workflow side, the isMeargeable value MUST be false, meaning it won't execute the merge action.


    name: 'Merge PR'
    needs: update_labels

    runs-on: ubuntu-latest

    - name: Merge PR
      if: ${{ github.event.inputs.isMergeable == 'true' }}
      shell: pwsh
      run: |
        $headers = @{ "Authorization" = "token ${{ secrets.WORKFLOW_DISPATCH_TOKEN }}"; "User-Agent" = "HackaLearn Bot"; "Accept" = "application/vnd.github.v3+json" }

        $owner = "devrel-kr"
        $repository = "HackaLearn"
        $issueId = "${{ github.event.inputs.prId }}"

        $url = "https://api.github.com/repos/$owner/$repository/pulls/$issueId"
        $pr = Invoke-RestMethod -Method Get -Uri $url -Headers $headers

        $sha = $pr.head.sha
        $title = ""
        $message = ""
        $merge = "squash"
        $body = @{ "commit_title" = $title; "commit_message" = $message; "sha" = $sha; "merge_method" = $merge; }

        $url = "https://api.github.com/repos/$owner/$repository/pulls/$issueId/merge"
        Invoke-RestMethod -Method Put -Uri $url -Headers $headers -Body $($body | ConvertTo-Json)


Power Automate Workflow


The challenge approval workflow calls this Power Automate workflow. Firstly, it checks the type of challenges. If no challenge is identified, it does nothing.


Challenge Update Flow 1


If the challenge is linked to the registered participant's GitHub ID, update the record on Microsoft Lists; otherwise, do nothing.


Challenge Update Flow 2


Finally, it sends a confirmation email using a different email template based on the number of challenges completed.


Challenge Update FLow 3


The Others: Other Power Automate Workflows


Previously described Power Automate workflows are triggered by GitHub Actions for integration. However, there are other workflows only for management purposes. As most processes are similar to each other, I'm not going to describe them all. Instead, it's the total number of workflows that I used for the event, which is 15 in total.


List of Power Automate Workflows


Now all my business processes are fully automated. As an operator, I can focus on questions and PR reviews, but nothing else.


The Stats


The HackaLearn even was over! Here are some numbers related to this HackaLearn event.


  • 14: Number of days for HackaLearn
  • 171: Total number of participants
  • 62: Total number of participants who completed Cloud Skills Challenge
  • 21: Total number of teams who uploaded social media posts
  • 17: Total number of teams who completed building Azure Static Web Apps
  • 16: Total number of teams who completed provided their GitHub repository
  • 20: Total number of teams who published their blog post as a retrospective
  • 13: Total number of teams who completed all six challenges


The Lessons Learnt


Surely, there are many spaces for future improvement. What I've learnt from the automation exercise are:


  • Do not assume the way participants create their PRs is expected

    :backhand_index_pointing_right: The review automation process should be as flexible as possible.

  • Make the review process as simple as possible

    :backhand_index_pointing_right: The reviewers should only be required to focus on the PR, not anything else.

  • Reviewer should be assigned to a team instead of individual PRs

    :backhand_index_pointing_right: If a reviewer is assigned to a team, the chance for merge conflicts will dramatically decrease.

  • Make Power Automate workflows as modular as possible

    :backhand_index_pointing_right: There are many similar sequence of actions across many workflows.
    :backhand_index_pointing_right: They can be modularised into either sub-workflows or custom APIs through custom connectors.


The Side Events


During the event, we ran live hands-on workshops for GitHub Actions and Azure Static Web Apps led by a GCE and an MLSA respectively.


  • Live Hands-on: GitHub Actions (in Korean)

  • Live Hands-on: Azure Static Web Apps (in Korean)

  • Live Hands-on: Azure Static Web Apps with Headless CMS (in Korean)



So far, I summarised what I've learnt from this event and what I've done for workflow automation, using GitHub Actions, Microsoft 365 and Power Automate. Although there are lots of spaces to improve, I managed to run the online hackathon event with a fully automated process. I can now do it again in the future.


Specially thanks to MLSAs and GCEs to review all the PRs, and mentors who answered questions from participants. Without them, regardless of the fully automated workflows, this event wouldn't be successfully running.


This article was originally published on Dev Kimchi.

1 Comment
Version history
Last update:
‎Aug 27 2021 06:17 AM
Updated by: