Home

Efficient Pull Request Reviews: How to Create Environments with GitHub Actions

7/15/2024

Image generated on DreamStudio by Milton Boos JuniorImage generated on DreamStudio by Milton Boos Junior

When reviewing pull request changes, many developers often encounter some difficulties. One of the biggest problems is that it can take time and effort to sift through an entire codebase to review the changes made to a specific component. This can slow down the code review process and make it more difficult for teams to collaborate effectively.

To streamline the process of reviewing pull request changes, you can create multiple GitHub pages — one for each pull request. This approach allows reviewers to focus on the visual and functional changes in isolation, without the distractions of unrelated changes in the codebase, making collaboration more efficient and effective.

Comparison main branch vs PR branch

Setup GitHub repository

The technologies we'll be using include React, Vite, Typescript, Storybook, GitHub Actions, and GitHub Pages. While you may be familiar with some of these, it's important to note that you can use other technologies as long as you are hosting your static websites on GitHub Pages, a free service that enables developers to host directly from their GitHub repositories.

If you are also using Vite, make sure to set the correct base in vite.config.js as it's recommended in the docs.

To get started, simply create a new GitHub repository that consists of a main branch containing your application's codebase, and a gh-pages branch that will serve as the host for all of your deployed environments. Be sure to create the gh-pages branch as an orphan branch, rather than based on your main branch, to avoid any unnecessary code being included in it.

The deployed environments will function as subpaths within the URL of the repository. For each pull request (PR), a corresponding folder will be created in the gh-pages branch. This folder will remain deployed for as long as the PR is active. Upon merging or closing the PR, the folder will be removed and the deployed page will be updated accordingly. Additionally, there will be a main folder that serves the environment of the main branch.

Make sure to configure your repo's GitHub Page source to use the branch gh-pages by default at <REPO_URL>/settings/pages. For the final step, add an empty .nojekyll file to make GitHub Pages stop ignoring some Storybook files and the following index.html to redirect users from https://<GITHUB_USER>.github.io/<REPO>/ to https://<GITHUB_USER>.github.io/<REPO>/main.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta
      http-equiv="refresh"
      content="0; URL=https://<GITHUB_USER>.github.io/<REPO>/main"
    />
  </head>
  <body></body>
</html>
gh-pages branch representation

gh-pages branch representation

GitHub Workflows

Comment on PR

GitHub Actions bot with the link to the environmentGitHub Actions bot with the link to the environment

The initial workflow required is the one to generate a comment on each pull request, containing the preview URL once Storybook has been deployed. This can be achieved using peter-evans/find-comment@v2 and peter-evans/create-or-update-comment@v2. It's worth noting that there are alternative workflows available that can produce the same outcome, so you are free to choose one that best suits your requirements.

#.github/actions/pr-comment/action.yml
name: PR comment find/create/update/post
description: "GitHub Action to find, create or update and post a comment to a PR"
 
inputs:
  msg:
    description: "Content of the comment"
    required: true
  header:
    description: "Header of bot comments for find comment action"
    required: true
 
runs:
  using: "composite"
  steps:
    - name: Find Comment
      uses: peter-evans/find-comment@v2
      id: fc
      with:
        issue-number: ${{ github.event.pull_request.number }}
        comment-author: "github-actions[bot]"
        body-includes: ${{ inputs.header }}
 
    - name: Create comment
      if: steps.fc.outputs.comment-id == ''
      uses: peter-evans/create-or-update-comment@v2
      with:
        issue-number: ${{ github.event.pull_request.number }}
        body: ${{ inputs.msg }}
        edit-mode: replace
 
    - name: Update comment
      if: steps.fc.outputs.comment-id != ''
      uses: peter-evans/create-or-update-comment@v2
      with:
        comment-id: ${{ steps.fc.outputs.comment-id }}
        body: ${{ inputs.msg }}
        edit-mode: replace

Deploy environments

This workflow involves pushing your environment to the gh-pages branch every time there's a push to either a pull request or the main branch. To prevent any concurrency issues arising from multiple commits, we'll also cancel any ongoing runs within the same group. Lastly, if the workflow was triggered by a pull request, the pr-comment action will be executed to update the pull request comment with a link to the deployed environment.

#.github/workflows/deploy.yml
name: Deploy to GitHub Pages
 
on:
  push:
    branches:
      - main
  pull_request:
    types: [opened, synchronize]
 
concurrency:
  group: storybook-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Setup Node 16
        uses: actions/setup-node@v3
        with:
          node-version: 16.15.1
      - name: Install Dependencies
        run: npm ci
      - name: Build
        run: npm run build-storybook
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@3.6.2
        with:
          BRANCH: gh-pages
          FOLDER: storybook-static
          CLEAN: true
          TARGET_FOLDER: ${{ github.head_ref || github.ref_name }}
      - name: Comment on PR
        if: github.event_name == 'pull_request'
        uses: ./.github/actions/pr-comment
        with:
          header: ":tada: Preview available!"
          msg: |
            :tada: Preview available!
            <https://<GITHUB_USER>.github.io/<REPO>/${{ github.head_ref }}>

Delete environments

After a pull request has been merged or closed, the corresponding folder should be deleted and the environment updated accordingly. To prevent any potential conflicts arising from the ongoing deployment of recent commits, the concurrency will cancel previous runs. At the Setup Git Config step, you'll be required to configure Git with your email and username, after which the workflow will proceed to remove the folder from the gh-pages branch and push the changes automatically.

#.github/workflows/clear-environment.yml
name: Clear environment
 
on:
  pull_request:
    types: [closed]
 
permissions:
  contents: write
 
concurrency:
  group: storybook-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true
 
jobs:
  remove-folder:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
        with:
          ref: gh-pages
      - name: Setup Git Config
        shell: bash
        run: |
          git config --global user.email "<GITHUB_EMAIL>"
          git config --global user.name "<GITHUB_USER>"
      - name: Remove ${{ github.head_ref }} folder
        run: rm -rf ${{ github.head_ref }}
      - name: Commit changes
        run: |
          # Commit removed folder
          git add .
          git commit -m "Remove ${{ github.head_ref }}" --no-verify
          git push origin gh-pages

Conclusion

Overall, utilizing GitHub Actions to create and manage multiple environments can greatly enhance the efficiency of your pull request review process. By following the workflows outlined in this article, you can automate the deployment of your codebase to separate environments for each pull request, making it easier to test and review changes. You can find a demo at my GitHub, give it a try, and experience the benefits for yourself!