개발자 경험 개선하기 (1) - PR 리뷰 마감시간을 알려주는 슬랙봇 구현하기(feat. Github Actions & Slack API)

보투게더·2023년 9월 4일
1
post-thumbnail

들어가며

보투게더 팀의 코드리뷰 규칙

보투게더 팀의 코드리뷰 규칙은 크게 3가지 정도가 있습니다.

  1. PR은 최소 2명 이상의 Approve가 있어야 merge될 수 있다.
  2. PR은 10시간 이내로 코드리뷰를 한다.
  3. PR 리뷰를 완료하거나 리뷰를 반영했다면코드리뷰 완료, 리뷰 반영 완료 등의 메시지를 슬랙에 남긴다.

위의 규칙 중 첫번째는 깃허브 레포의 Settings에서 상세히 설정해주면 되지만, 2, 3번과 같은 규칙은 자동화하려면 Github Actions와 Slack API를 활용해야 했습니다.

더불어 2번의 10시간이라는 기준이, 단순히 PR 올린 시간에 10시간 더한 결과가 아니었습니다. 보투게더 팀은 워라밸을 중시하기 때문에, 근무시간이평일 오전 10시 ~ 오후 10시로 정해져 있습니다.

주말에는 좀 쉬자!! 코드리뷰 No~~

금요일 오후와 주말에 pr을 올리면, 마감시간은 월요일 오전 10시에 10을 더해서 월요일 20시가 됩니다.
예를 들어 목요일 새벽 2시에 pr을 올리면, 목요일 오전 10시에 10시간을 더해서 목요일 20시가 코드리뷰 마감시간이 됩니다.

수요일 오전 9시 30분 -> 마감시간: 수요일 오후 7시 30분 
목요일 오전 10시 30분 -> 마감시간: 목요일 오후 8시 30분
목요일 13시 30분 -> 마감시간: 금요일 오전 11시 30분
목요일 15시 30분 -> 마감시간: 금요일 13시 30분
금요일 15시 30분 -> 마감시간: 월요일 13시 30분
금요일 21시 30분 -> 마감시간: 월요일 19시 30분
토요일 오전 10시 30분 -> 마감시간: 월요일 20시 00분
일요일 18시 30분 -> 마감시간: 월요일 20시 00분
월요일 오전 10시 30분 -> 마감시간: 월요일 20시 30분

이런 계산을 머리로 직접 해서, 아래처럼 슬랙에 올리는데.. 계속 하다 보면 익숙해질 수 있겠지만 두 달 해보니까 자동화 꼭 해보고 싶은데? 라는 생각이 들더라구요😅🤔

보투게더 기존 코드리뷰 방식1 보투게더 기존 코드리뷰 방식2

🔼보투게더 팀의 기존 코드리뷰 모습 (슬랙에 마감시간 계산해서 올림)

또 코드리뷰를 했다는 댓글(리뷰 완등)을 달려고 할 때, pr에 대한 댓글을 찾아서 스레드로 달고 있는데 이런 과정이 다소 귀찮고 불편하다고 느꼈습니다.

이렇게 사소하게 매뉴얼한 부분도 편리하게 자동화한다면, 코드리뷰 효율도 올라간다고 생각했구요!!

따라서 아래의 과정들을 거쳐 코드리뷰 마감시간을 알려주는 슬랙 봇, 코멘트를 달면 슬랙에 메시지를 보내는 기능 2가지를 만들어봤습니다😀

1. Github API 와 yml 파일로 Gihtub Actions 트리거하기

Github Actions 란?
▶ CI(지속 통합) 또는 CD(지속 배포)와 같은 자동화를 위한 툴로, workflow에서 반복적으로 처리되는 여러 작업(job)에 대한 매커니즘을 구성할 수 있습니다.

Github Webhook

Webhook 이란?
▶ Github에서 특정 이벤트들이 발생하면, 외부 웹서버로 알림들이 전달되도록 하는 방법

✔ 특정 이벤트들의 예시는 아래와 같아요.
1) 코드가 repo 에서 push 되는 경우
2) pr이 open된 경우
3) Github Pages 사이트가 배포된 경우
4) 새로운 멤버가 팀에 합류한 경우

우리 팀은 pr을 올렸을 때 어떤 workflow가 일어나길 원하기 때문에, Webhook event는 pull request가 됩니다. 따라서 Github Workflow Trigger에 대한 Github Docs를 참고하여 구성해봤습니다.

frontend-pr-deadline-slack-bot.yml로 Github Actions을 트리거한 로직은 아래와 같습니다.

name: Notify Pull Request Deadline (FE)

on:
  pull_request:
    types:
      - opened // open된 pr인 경우
    branches: ['dev'] // 다른 브랜치에서 dev 브랜치를 향하는 경우
    paths:
      - 'frontend/**' // frontend 폴더에서 변경사항이 일어난 경우


jobs:
  pull_request_open:
    runs-on: ubuntu-latest
    name: New pr to repo
    steps:
      - name: Checkout code
        uses: actions/checkout@v2 // Github repo에 올려둔 코드를 CI 서버로 내려받은 후에 특정 branch로 전환하는 행위

      - name: Set environment variable // github 환경변수 세팅
        run: echo "PR_CREATED_AT_UTC=${{ github.event.pull_request.created_at }}" >> $GITHUB_ENV  // .env 파일에 환경변수 생성 
        
      - name: Convert UTC to KST  // pr 생성 시간 포맷을 UTC에서 KST(한국 기준)로 변경
        run: |
          UTC_TIME=$PR_CREATED_AT_UTC
          KST_TIME=$(date -u -d "$UTC_TIME 9 hour" "+%Y-%m-%dT%H:%M:%SZ")
          echo "PR_CREATED_AT_KST=$KST_TIME" >> $GITHUB_ENV // .env 파일에 환경변수 생성

2. 마감시간 계산 로직 구현하기 (feat. javascript)

이제 PR 생성 시간(한국 기준)을 가지고 코드리뷰 마감시간을 계산해봅시다. 들어가며에서 설명했듯이 우리 팀만의 컨벤션을 적용해서 복잡한 로직을 구성하려면,, 대체 yml 파일만으로 어떻게 구현해야 하지??

걱정이 많았으나.... 역시나 yml 문법만으로 복잡한 수학적 계산을 하는 것은 살짝 한계가 있었습니다.

마감시간 계산하는 로직은 javascript로 하고, 계산된 결과값만 yml 파일에서 가져와서 활용해보자!

🔽calculatePRDeadline.js

function calculatePRDeadline(prCreatedAtKST) {
  const prCreatedAt = new Date(String(prCreatedAtKST));

  const prCreatedMinute = prCreatedAt.getUTCMinutes();
  const prCreatedHour = prCreatedAt.getUTCHours();
  const prCreatedDate = prCreatedAt.getUTCDate();
  const prCreatedDay = prCreatedAt.getUTCDay();
  const prCreatedMonth = prCreatedAt.getUTCMonth() + 1; // getUTCMonth()는 0부터 시작하므로 1을 더해줍니다.

  const isFridayAfternoon = prCreatedDay === 5 && prCreatedHour >= 22; // 금요일 오후 10시 이후 (금요일: 5, 오후 12시: 12)
  const isWeekend = prCreatedDay === 6 || prCreatedDay === 0; // 주말인 경우
  const isMondayMorning = prCreatedDay === 1 && prCreatedHour < 10; // 월요일 오전 10시 이전 (월요일: 1, 오전 10시: 10)

  // 주어진 근무시간(월요일 오전 10시~금요일 오후 10시) 내에 올린 pr인지 판별
  const isNotWorkingTime = isFridayAfternoon || isWeekend || isMondayMorning;

  let nextDay = new Date(prCreatedAt);
  nextDay.setUTCDate(prCreatedDate + 1); // 다음 날의 날짜를 설정합니다.

  const nextDayDate = nextDay.getUTCDate();
  const nextDayMonth = nextDay.getUTCMonth() + 1;

  let nextWeekMonday = new Date(prCreatedAt);
  const daysUntilMonday = 8 - prCreatedDay;
  nextWeekMonday.setUTCDate(
    prCreatedDay === 0 ? prCreatedDate + 1 : prCreatedDate + daysUntilMonday
  );
  const nextWeekMondayDate = nextWeekMonday.getUTCDate();
  const nextWeekMondayMonth = nextWeekMonday.getUTCMonth() + 1;

  const isFriday = prCreatedDay === 5;

  if (isNotWorkingTime)
    return `${nextWeekMondayMonth}${nextWeekMondayDate}일 20시 00분`;

  if (prCreatedHour < 10 && prCreatedHour > 0)
    return `${prCreatedMonth}${prCreatedDate}일 20시 00분`;
  else if (prCreatedHour === 22 || prCreatedHour === 23)
    return `${nextDayMonth}${nextDayDate}일 20시 00분`;
  else if (prCreatedHour >= 12)
    return `${isFriday ? nextWeekMondayMonth : nextDayMonth}${
      isFriday ? nextWeekMondayDate : nextDayDate
    }${prCreatedHour - 2}${prCreatedMinute}`;
  else
    return `${isFriday ? nextWeekMondayMonth : prCreatedMonth}${
      isFriday ? nextWeekMondayDate : prCreatedDate
    }${prCreatedHour + 10}${prCreatedMinute}`;
}

console.log(
  `::set-output name=DEADLINE::${calculatePRDeadline(
    process.env.PR_CREATED_AT_KST
  )}`
); // yml 파일에서 github env로 생성한 환경변수

이제 calculatePRDeadline이 return 하는 마감시간 값을, yml 파일에서 가져와 사용하려면 어떻게 해야 할까요?

마지막에 console.log로 DEADLINE 이라는 key 값에, 마감시간 value를 담아서 출력하면 됩니다.

또 yml 파일에 아래의 코드를 추가해줍니다.

      - name: Calculate deadline
        id: deadline
        run: node .github/workflows/scripts/calculatePRDeadline.js
        env:
          PR_CREATED_AT_KST: ${{ env.PR_CREATED_AT_KST }}

3. Slack API 이용하여 마감시간 알림봇 만들기

🤖Slack App(Bot) 생성하기

Slack App(Bot) 생성하기
Slack App(Bot)을 생성하고 보투게더 워크스페이스에 설치, 특정 채널에 초대해줍니다. slack-app-workspace slack-app-invite

슬랙봇을 워크스페이스에 성공적으로 추가했으면, 마지막으로 yml에 아래의 코드를 추가해줍니다.

      - name: Send Slack notification When FE PR
        uses: slackapi/slack-github-action@v1.24.0  // slack api 를 이용하기
        with:
          channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID
          payload: |
            {
                "text": "",
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": "<${{ steps.remove_https.outputs.pr_url }}|${{ github.event.pull_request.title }}>\n코드리뷰 마감시간: ${{ steps.deadline.outputs.DEADLINE }}"
                        }
                    }
                ]
            } // id: deadline 에서 calculatePRDeadline.js가 출력하는 DEADLINE 값 가져오기
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Github Secret에 설정한 Slack 토큰 값 

slack-github-actions API에 대한 자세한 설명 및 공식 문서는 요 repo에서 확인하실 수 있습니다😀

🔑Github Secret 설정하기

github repo 에서 Settings-Secrets-Actions secrets and variables 로 들어가보면, New Repository Secret 이라는 버튼이 보입니다.
이 버튼을 눌러 새로운 환경변수를 추가해줍시다.New Secret

채널 ID는 어떻게 알 수 있나요?
▶ 브라우저로 슬랙 워크스페이스 들어가면 url의 마지막 파라미터가 해당 슬랙 채널 ID 값입니다!

보투게더 팀은
1. 프론트엔드 코드리뷰 채널 ID (SLACK_FE_CHANNEL),
2. 백엔드 코드리뷰 채널 ID (SLACK_BE_CHANNEL),
3. 슬랙봇 토큰 (슬랙 홈페이지에서 Copy한 Bot Token, SLACK_TOKEN)

이렇게 3가지의 Github Secret을 추가해주었습니다!
그러면 아래와 같이 추가해준 환경변수 목록을 볼 수 있습니다~ secret key list

이제 yml 파일 전체 코드를 살펴봅시다.

name: Notify Pull Request Deadline (FE)

on:
  pull_request:
    types:
      - opened
    branches: ['dev']
    paths:
      - 'frontend/**'

jobs:
  pull_request_open:
    runs-on: ubuntu-latest
    name: New pr to repo
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Remove "https://" from PR URL
        id: remove_https
        run: |
          PR_URL="${{ github.event.pull_request.html_url }}"
          PR_URL="http://${PR_URL#https://}"          
          echo "::set-output name=pr_url::$PR_URL"

      - name: Set environment variable
        run: echo "PR_CREATED_AT_UTC=${{ github.event.pull_request.created_at }}" >> $GITHUB_ENV
      - name: Convert UTC to KST
        run: |
          UTC_TIME=$PR_CREATED_AT_UTC
          KST_TIME=$(date -u -d "$UTC_TIME 9 hour" "+%Y-%m-%dT%H:%M:%SZ")
          echo "PR_CREATED_AT_KST=$KST_TIME" >> $GITHUB_ENV

      - name: Calculate deadline
        id: deadline
        run: node .github/workflows/scripts/calculatePRDeadline.js
        env:
          PR_CREATED_AT_KST: ${{ env.PR_CREATED_AT_KST }}

      - name: Send Slack notification When FE PR
        uses: slackapi/slack-github-action@v1.24.0
        with:
          channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID
          payload: |
            {
                "text": "",
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": "<${{ steps.remove_https.outputs.pr_url }}|${{ github.event.pull_request.title }}>\n코드리뷰 마감시간: ${{ steps.deadline.outputs.DEADLINE }}"
                        }
                    }
                ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰

위 코드에서 별도로 추가한 코드가 있는데요,

      - name: Remove "https://" from PR URL
        id: remove_https
        run: |
          PR_URL="${{ github.event.pull_request.html_url }}"
          PR_URL="http://${PR_URL#https://}"          
          echo "::set-output name=pr_url::$PR_URL"

PR의 url을 httpshttp 로 바꿔주는 코드입니다. 슬랙에서는 링크를 치면 링크에 대한 미리보기가 아래처럼 뜨는데, 링크가 공간을 많이 잡아먹어서 메시지를 찾기가 힘들더라구요.. 그런데 링크가 http로 시작하는 경우에는 미리보기가 생기지 않아서, 일부러 http로 바꿔주었습니다!👍

4. Actions 탭에서 Action이 일어나는 과정 확인하기

repo에서 Actions 탭을 눌러서 Worflow(Webhook event)가 잘 trigger되는지 확인할 수 있습니다.
Actions-All workflows listactions-job steps

💡보너스! pr에 코멘트 남기면 슬랙 알림 가도록 하기

지금까지는 슬랙 스레드에 리뷰 완! 또는 피드백 반영했습니다~ 등의 댓글을 남겼었는데요, 스레드를 찾아서 위로 스크롤하기 불편하다고 느꼈습니다.

pr에 코멘트만 남기면, 자동으로 슬랙에 알림에 가도록 하면 어떨까? 라는 생각이 들었고 코멘트 봇 만들기 라는 글을 참고하여 아래와 같이 yml 파일을 작성했습니다.

  • frontend-pr-comment.yml
name: Pull request comment (FE)
on:
  issue_comment:
    types: [created, edited, deleted]

jobs:
  pull_request_comment:
    # This job only runs for pull request comments
    if: ${{ github.event.issue.pull_request }}
    runs-on: ubuntu-latest
    steps:
      - name: Send Slack notification When Review Completed
        if: contains(github.event.comment.body, 'review-complete') # check the comment if it contains the keywords
        uses: slackapi/slack-github-action@v1.24.0
        with:
          channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID
          payload: |
            {
                "text": "",
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": "리뷰 완료했습니다👍\n<${{ github.event.comment.html_url }}|리뷰어의 코멘트 확인하러 가기>"
                        }
                    }
                ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰

      - name: Send Slack notification When Re-review Requested
        if: contains(github.event.comment.body, 'review-request') # check the comment if it contains the keywords
        uses: slackapi/slack-github-action@v1.24.0
        with:
          channel-id: ${{ secrets.SLACK_FE_CHANNEL }} # Slack 채널 ID
          payload: |
            {
                "text": "",
                "blocks": [
                    {
                      "type": "section",
                      "text": {
                        "type": "mrkdwn",
                        "text": "리뷰 반영 최종 완료!✅ 확인 부탁드립니다😃\n<${{ github.event.comment.html_url }}|피드백 반영 확인하러 가기>"
                      }
                    }
                  ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} # Slack 토큰

issue_comment라는 Webhook event를 활용해서 worflow를 trigger 하는 로직인데요, 아래 이미지처럼 pr에 review-complete라는 댓글을 달아보면...
review-complete-comment
Actions 탭에서 workflow가 잘 일어나고 있네요👍👍
issue-comment-workflow slack_message

마치며

github와 slack API 를 활용하여 슬랙 알림 자동화를 구현해봤는데요! 코드리뷰 효율을 올릴 수 있어 정말 뿌듯하네요😆 슬랙봇 구현하면서 평소에 미루던 github actions 공부도 제대로 할 수 있었고, yml 문법도 배워볼 수 있었습니다👍

추가로 개선해볼 점?

하나의 yml 파일에서, frontend, backend 폴더의 변경 사항에 따라 분기를 나눠서 슬랙 알림을 보내보려고 시도했는데, yml 문법에 익숙하지 않아 if를 활용해서 분기를 나누는 것에 실패했습니다..😂
따라서 frontend-pr-comment.yml, backend-pr-comment.yml 이렇게 2개의 파일로 나눴는데요, 하나의 파일에서도 분기 처리할 수 있는 방법을 좀 더 알아보면 좋겠네요🤔😀 (혹시 아시는 분이 계신다면 댓글 달아주시면 감사하겠습니다!!)

🔎구현한 코드 보러 가기 🔽🔽

https://github.com/woowacourse-teams/2023-votogether/tree/dev/.github/workflows

참고자료

profile
Fun from Choice! 오늘도 즐거운 한 표

1개의 댓글

comment-user-thumbnail
2023년 9월 13일

최고에요!

답글 달기