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