Notion TASK to Git

2023. 11. 1. 18:51개발/최적화

Notion TASK to Git

💡
현재 git issue state는 issue open → notion table 동기화로 되어있음 여기에 추가적으로, 스프린트(개발 그룹)이 시작된 task에 한하여 issue를 자동으로 만들어주는 스크립트를 작성하고자 함

스택

  • js
  • notion client
  • github rest api
  • octokit

요구사항

  • 상태가 진행 중인 task를 가져갈 것
  • 플랫폼이 android 인 task를 가져갈 것
  • task에서 뽑아낼 내용은
    • task id + task title → issue title
  • 같은 id를 가진 issue 는 skip. 만들지 않을 것
  • 문서, 회의 등을 제외시킬것
    • 이슈 대응 및 신규 개발로 구분된 task만 등록할것

개발내용

  • 초기 세팅
    • 루트의 .env 파일에 아래 속성 추가
    NOTION_TASK_DATABASE_ID="id"
    • 루트에 task-to-issue.js 추가
    const { Client } = require("@notionhq/client")
    const dotenv = require("dotenv")
    const { Octokit } = require("octokit")
    const _ = require("lodash")
    
    dotenv.config()  
    
    const octokit = new Octokit({ auth: process.env.GITHUB_KEY })
    const notion = new Client({ auth: process.env.NOTION_KEY })
    
    const databaseId = process.env.NOTION_TASK_DATABASE_ID
    
    const gitHubIssueIdToNotionTaskId = {}
    
    getInitialIssues().then(syncNotionDatabaseWithGitHub)
    
    //정규식 적용해 존재하는 issue중 task id 포함한것만 가져옴
    //이슈 중복생성 막기 위함
    async function getInitialIssues() {
      const regex = /INTASK-\d+/g;
      const currentIssues = await getGitHubIssuesForRepository(); // 비동기 호출
      for (const issue of currentIssues) {
        gitHubIssueIdToNotionTaskId[(issue.title.match(regex) || '-')] = {
          state: issue.state
        };
      } 
    }
    async function syncNotionDatabaseWithGitHub() {
      console.log("\nFetching issues from GitHub repository...")
    
      const tasks = await getIssuesFromNotionDatabase()
      console.log(`Fetched ${tasks.length} issues from Notion Database.`)
    
      const issueToCreate = getNotionOperations(tasks)
    
      await postGitHubIssues(issueToCreate)
    
      // Success!
      console.log("\n�� Notion database is synced with GitHub.")
    }
    
    async function getNotionOperations(tasks) {
      const issuesToCreate = []
      for(const task of tasks) {
        const isIssueCreated = gitHubIssueIdToNotionTaskId[task.taskId]
        if(!isIssueCreated) issuesToCreate.push(task)
      }
       return issuesToCreate
    }
    
    /**
     *
     * @returns {Promise<Array<{ pageId: string, issueNumber: number }>>}
     */
    async function getIssuesFromNotionDatabase() {
      const pages = []
      let cursor = undefined
      while (true) {
        const { results, next_cursor } = await notion.databases.query({
          database_id: databaseId,
          start_cursor: cursor,
        })
        pages.push(...results)
        if (!next_cursor) {
          break
        }
        cursor = next_cursor
      }
      console.log(`${pages.length} tasks successfully fetched.`)
    
      const tasks = []
    //조건을 만족하는 task만 issue로 올림(개발이 필요한것)
      for (const page of pages) {
        const state = page.properties["상태"].status.name
        const category = page.properties["구분"].multi_select[0].name
        const taskId = page.properties["TASK-ID"].unique_id
        const title = page.properties["이름"].title[0]
        const platform = page.properties["플랫폼"] 
        if (title && title.plain_text && platform && platform.select.name ) {
          const needDev = (category == "이슈" || category == "신규개발" || category == "qa") && (platform.name == "Android" || platform == "공통")
         if(needDev && state == "진행중") {
          tasks.push({
            pageId: page.id,
            taskId: taskId.prefix + "-" + taskId.number,
            title: title.plain_text,
          })
        }
        } else {}   
       }
       return tasks
    }
    
    /**
     *
     *
     * @returns {Promise<Array<{ umber: number, title: string, platform: string, status: string }>>}
     */
    async function getGitHubIssuesForRepository() {
      const issues = []
      const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
        owner: process.env.GITHUB_REPO_OWNER,
        repo: process.env.GITHUB_REPO_NAME,
        state: "all",
        per_page: 100,
      })
      for await (const { data } of iterator) {
        for (const issue of data) {
          if (!issue.pull_request) {
            issues.push({
              number: issue.number,
              title: issue.title,
              state: issue.state,
              comment_count: issue.comments,
              url: issue.html_url,
            })
          }
        }
      }
      return issues
    }
    
    async function postGitHubIssues(issueToCreate) {
      const issueToCreateData = await issueToCreate;
      const issueToCreateChunks = _.chunk(issueToCreateData, 10);
    
      for (const issuesToCreateBatch of issueToCreateChunks) {
        await Promise.all(
          issuesToCreateBatch.map(async (issue) => {
            const title = getTitleFromTask(issue);
            await octokit.rest.issues.create({
              owner: process.env.GITHUB_REPO_OWNER,
              repo: process.env.GITHUB_REPO_NAME,
              title: title,
            });
          })
        );
        console.log(`Completed batch size: ${issuesToCreateBatch.length}`);
      }
    }
    
    function getTitleFromTask(task) {
       return  task.title + "/" + task.taskId
    }

  • 사용 방법
    • 회고 회의 후, 스프린트에 포함되는 task를 진행중으로 변경
    • script를 터미널에서 아래와 같이 실행
    node task-to-issue.js

  • 결과

→ 자동으로 진행중, open된 task를 올린다

→ 해당 이름 기반으로 branch를 만들고, pr이 끝나면 notion에서 “완료”로 자동적으로 상태가 변경된다.

TODO

  • 차후 연관 issue에 자동으로 연동시키는 기능을 추가할 예정입니다.


Uploaded by N2T

'개발 > 최적화' 카테고리의 다른 글

Ktlint + detekt  (0) 2023.07.25
Commit Convention  (0) 2023.07.25
뷰모델 중복 이슈 해결  (0) 2023.05.03
QA / release모드 변경  (0) 2023.05.03
Delegate Pattern  (0) 2023.05.03