Site icon TheWindowsUpdate.com

Hackathon Automation with GitHub Actions, Microsoft 365 and Power Platform

This post has been republished via RSS; it originally appeared at: New blog articles in Microsoft Tech Community.

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.

 

 

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.

 

 

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.

 

 

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.

 

 

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.

 

 

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.

 

 

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

on:
  workflow_dispatch:
    inputs:
      teamName:
        description: The name of team
        required: true
        default: Team_HackaLearn
      content:
        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
      with:
        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
      with:
        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.

 

 

  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

on:
  pull_request_target:
    types:
    - opened
    branches:
    - main
    paths:
    - '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.

 

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

    runs-on: ubuntu-latest

    steps:
    - 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
      with:
        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
      with:
        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
      with:
        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
      with:
        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
      with:
        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
      with:
        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

on:
  pull_request_target:
    types:
    - labeled
    - unlabeled

jobs:
  labelling:
    name: 'Update a label'

    runs-on: ubuntu-latest

    steps:
    - name: Respond to label
      uses: dessant/label-actions@v2
      with:
        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

on:
  issue_comment:
    types:
    - 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).

 

env:
  PR_REVIEWERS: ${{ secrets.PR_REVIEWERS }}

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

    runs-on: ubuntu-latest

    steps:
    - 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
  with:
    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
  with:
    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
  with:
    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
  with:
    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
  with:
    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
  with:
    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

on:
  workflow_dispatch:
    inputs:
      prId:
        description: PR ID
        required: true
        default: ''
      labelsToAdd:
        description: The comma delimited labels to add
        required: true
        default: record-updated
      labelsToRemove:
        description: The comma delimited labels to remove
        required: true
        default: review-completed
      isMergeable:
        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.

 

jobs:
  update_labels:
    name: 'Update labels'

    runs-on: ubuntu-latest

    steps:
    - 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.

 

  merge_pr:
    name: 'Merge PR'
    needs: update_labels

    runs-on: ubuntu-latest

    steps:
    - 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.

 

 

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

 

 

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

 

 

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.

 

 

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.

 

 

The Lessons Learnt

 

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

 

 

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.

 

 


 

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.

Exit mobile version