白居易在看到滿滿的 issue 後不經感嘆:「野火燒不盡,春風吹又生。」然後毅然決然地把 issue 同步到 Notion,這樣就不會忘記了。

今日範例程式:https://github.com/Edit-Mr/2024-GitHub-Actions/tree/main/18

今天,我們將介紹如何編寫一個自定義的 GitHub Actions,將 GitHub 的 issue 自動同步到 Notion。這個工具將使你能夠將 GitHub 的問題跟踪和管理整合到 Notion 的資料庫中,讓問題管理更加高效。

Demo
Demo

實作

事前準備:建立 Notion 整合

首先請你參考昨天的步驟,建立一個 Notion 整合。請至 Notion Developers 創建一個新的整合,並獲取 API 密鑰,然後整合至 Workspace。這個 API 密鑰將用於訪問 Notion 的 API,以便將 GitHub 的 issue 同步到 Notion 中。

步驟 1:設置專案結構

首先,創建一個新的 GitHub 存儲庫來容納我們的自定義 Action。結構如下:

1github-issue-2-notion/
2├── action.yml
3├── script.js
4└── README.md

今天我們直接使用 Node.js 來編寫。

步驟 2:編寫 Action 配置文件

action.yml 文件中,定義 Action 的輸入、執行和輸出。以下是 action.yml 的內容:

1name: "Sync GitHub Issues to Notion"
2description: "Synchronize GitHub issues to a Notion database"
3inputs:
4    repo:
5        description: "The GitHub repository (e.g., owner/repo)"
6        required: true
7    NOTION_API_KEY:
8        description: "The API key for the Notion integration"
9        required: true
10    NOTION_DATABASE_ID:
11        description: "The ID of the Notion database"
12        required: true
13runs:
14    using: "node20"
15    steps:
16        - name: Run script
17          uses: actions/setup-node@v3
18          with:
19              node-version: "20"
20        - run: npm install
21        - run: node script.js
22          env:
23              GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24              NOTION_API_KEY: ${{ inputs.NOTION_API_KEY }}
25              NOTION_DATABASE_ID: ${{ inputs.NOTION_DATABASE_ID }}

步驟 3:編寫 Node.js 腳本

script.js 文件中,編寫 Node.js 腳本來實現從 GitHub 獲取 issue 並同步到 Notion。以下是 script.js 的完整內容:

1/** @format */
2
3const core = require("@actions/core");
4const axios = require("axios");
5const { markdownToBlocks } = require("@tryfabric/martian");
6
7async function main() {
8    const repo = core.getInput("repo");
9    const notionToken = core.getInput("NOTION_API_KEY");
10    const notionDatabaseId = core.getInput("NOTION_DATABASE_ID");
11
12    // GitHub Issues API URL
13    const issuesUrl = `https://api.github.com/repos/${repo}/issues?state=all`;
14
15    // Fetch issues from GitHub
16    const issuesResponse = await axios.get(issuesUrl, {
17        headers: {
18            "User-Agent": "request",
19            Authorization: `token ${process.env.GITHUB_TOKEN}`
20        }
21    });
22
23    for (const issue of issuesResponse.data) {
24        const issueId = issue.id;
25        const notionUrl = `https://api.notion.com/v1/databases/${notionDatabaseId}/query`;
26
27        // Check if the issue already exists in Notion
28        const notionResponse = await axios.post(
29            notionUrl,
30            {
31                filter: {
32                    property: "ID",
33                    number: {
34                        equals: issueId
35                    }
36                }
37            },
38            {
39                headers: {
40                    Authorization: `Bearer ${notionToken}`,
41                    "Notion-Version": "2022-06-28",
42                    "Content-Type": "application/json"
43                }
44            }
45        );
46
47        const body = {
48            parent: { database_id: notionDatabaseId },
49            icon: {
50                emoji: "⚡"
51            },
52            properties: {
53                Name: {
54                    title: [
55                        {
56                            text: {
57                                content: issue.title
58                            }
59                        }
60                    ]
61                },
62                ID: {
63                    number: issueId
64                },
65                State: {
66                    select: {
67                        name:
68                            issue.state.charAt(0).toUpperCase() +
69                            issue.state.slice(1)
70                    }
71                },
72                Status: {
73                    status: {
74                        name: "Not started"
75                    }
76                },
77                Labels: {
78                    multi_select: issue.labels.map((label) => ({
79                        name: label.name
80                    }))
81                },
82                URL: {
83                    url: issue.html_url
84                }
85            },
86            children: issue.body != null ? markdownToBlocks(issue.body) : []
87        };
88
89        if (notionResponse.data.results.length > 0) {
90            console.log(
91                `Issue ${issueId} already exists in Notion, updating it`
92            );
93            // Update existing issue
94            const notionPageId = notionResponse.data.results[0].id;
95            delete body.properties.Status;
96            await axios.patch(
97                `https://api.notion.com/v1/pages/${notionPageId}`,
98                body,
99                {
100                    headers: {
101                        Authorization: `Bearer ${notionToken}`,
102                        "Content-Type": "application/json",
103                        "Notion-Version": "2022-06-28"
104                    }
105                }
106            );
107        } else {
108            console.log(`Creating new issue ${issueId} in Notion`);
109            // Create new issue
110            await axios.post("https://api.notion.com/v1/pages", body, {
111                headers: {
112                    Authorization: `Bearer ${notionToken}`,
113                    "Content-Type": "application/json",
114                    "Notion-Version": "2022-06-28"
115                }
116            });
117            console.log(`Issue ${issueId} created in Notion`);
118        }
119    }
120}
121
122main().catch((error) => {
123    console.error(error);
124    process.exit(1);
125});

這裡要來介紹一下裡面使用到的一些套件:

  • axios: 用於發送 HTTP 請求。
  • @tryfabric/martian: 用於將 Markdown 轉換為 Notion 的 block。
  • @actions/core: 用於訪問 Action 的輸入和輸出。

步驟 4:設置 GitHub Secrets

在 GitHub 存儲庫的設置中,添加以下 Secrets:

  • NOTION_API_KEY: 你的 Notion API 密鑰。
  • NOTION_DATABASE_ID: 你的 Notion 資料庫 ID。

步驟 5:創建工作流程文件

在你的 GitHub 存儲庫的 .github/workflows 目錄下創建一個新的 YAML 文件,例如 sync-issues.yml,並添加以下內容:

1name: Sync GitHub Issues to Notion
2
3on:
4    issues:
5        types: [opened, edited, deleted, closed, reopened]
6    workflow_dispatch:
7
8jobs:
9    sync:
10        runs-on: ubuntu-latest
11        steps:
12            - name: Checkout repository
13              uses: actions/checkout@v3
14
15            - name: Sync issues to Notion
16              uses: ./path-to-your-action # 使用自定義 Action 的路徑
17              with:
18                  repo: ${{ github.repository }}
19                  NOTION_API_KEY: ${{ secrets.NOTION_API_KEY }}
20                  NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}

這個工作流程將在 GitHub 的 issue 有變化時觸發,並運行我們的自定義 Action 來同步 issue 到 Notion。

小結

今天我們探討了如何編寫一個 GitHub Actions 來將 GitHub 的 issue 同步到 Notion。通過這個實踐,我們掌握了如何使用 Node.js 和 GitHub Actions 來自動化從 GitHub 獲取問題並更新 Notion 資料庫的過程。希望這篇教程能幫助你更高效地管理問題並保持項目組織的井然有序。

毛哥EM

一位喜歡把簡單的事,做得不簡單的高三生。