Notion APIを使ってGitHubのissueをお引越ししてみました
こんにちは。kubopです。
Notionでタスク管理をする際に、GitHubの既存のissueをどうするか、どう移行すれば良いかという問題にあたり、その備忘として記事を書きます。
(備忘録故、綺麗なコードじゃなくて申し訳ないのですが…!
要件
issueにある情報をそのままNotionに引き継ぐこと。
ラベル
アサイン
issueのURL
issueの中身(description)
issueに投稿されたコメント
descriptionはNotionのサブページに記載すること。
notionレコードに新規プロパティを追加してデータとして扱えるようにする。
手段の候補
GitHubのissueをお引っ越しするにあたり、いくつか候補がありました。
NotionにGitHubを連携する。
NotionのCSVインポート機能を利用する。
Notion APIを利用してページを作成する。
NotionにGitHubを連携する。
こちらに関しては、以下の問題があり断念しました。
GitHubのorg単位でしか権限管理ができなく、repo単位で権限管理が出来ない。
連携したissueはread onlyであり、notion側では編集できない。
連携したissueに新しくプロパティを追加できない。
NotionのCSVインポート機能を利用する。
こちらに関しては、以下の問題があり断念しました。
CSVインポートした場合は、issueのdescriptionが移行できない。
CSVの1カラムが1つのプロパティとして判定されてしまい、データとして扱うには難しい。
Notion APIを利用してページを作成する。
これは、GraphQLを用いてGitHubのissueを引き抜き、CSVに変換した上でNotion 公式のAPI SDKを利用してNotionにページを作成する方法です。
この方法であると、以上の問題点は全て解消できますが、デメリットとしてNotionからGitHubに戻ることが出来なくなります。(頑張って手動移行すれば戻れるかも・・・?)
結果的にGitHubのissueをGraphQLで引き抜き、NotionのAPIを用いてNotionにページを作成するという形で解決となりました。
実際に行ったこと
GitHubのissueをGraphQLで引き抜く
今回はRubyとgraphql-clientのgemを利用しています。
何回も実行するものではないので、全て同一ファイルにまとめています。
元々別案件でRubyからGraphQLを利用してCSVに変換していたため、そのままRubyを使っています。
100件を超える場合、ページネーション処理を追加する必要があります。
GitHubのAPIを利用する際に用いる設定部分です。
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'graphql-client'
end
require 'graphql/client'
require 'graphql/client/http'
require "csv"
module GitHubAPI
HTTP = GraphQL::Client::HTTP.new('https://api.github.com/graphql') do
def headers(context)
{
"Authorization" => "Bearer #{ENV['GITHUB_PAT']}"
}
end
end
Schema = GraphQL::Client.load_schema(HTTP)
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end
Issuesクラスを定義します。
クエリについては、Explorerを参考に作っています。
class Issues
QUERY = GitHubAPI::Client.parse <<-GraphQL
query($query: String!) {
search(type: ISSUE, query: $query, first: 100) {
pageInfo {
startCursor
hasNextPage
endCursor
},
nodes {
... on Issue {
number,
title,
url,
labels(first: 100) {
nodes { name }
},
bodyText,
comments(first: 100) {
nodes { body }
}
}
}
}
}
GraphQL
def initialize(args)
@org = args[:org]
@repo = args[:repo]
end
def data
res = exec_query
result = []
res.data.search.nodes.each { result << _1 }
result
end
def query
"is:issue is:open repo:#{@org}/#{@repo}" #今回はopenしてあるissueのみ対象にしています。
end
def exec_query
GitHubAPI::Client.query(QUERY, variables: { query: query })
end
end
CSVを生成します。
issues.csvは任意の名前にします。
issues = Issues.new(repo: ENV['REPO'], org: ENV['ORG']).data
CSV.open('issues.csv', 'w') do |csv|
issues.each do |issue|
row = [
issue.number,
issue.state,
issue.title,
issue.url,
issue.labels.nodes.map { |label| label.name }.join('/'),
issue.body_text,
issue.comments.nodes.map { |comment| comment.body }
]
csv << row
end
end
Notion APIのトークンを作成する
公式リファレンスはこちら
こちらからAPIのトークンを作成してください。
今回はページの作成のみ行うのでコンテンツの更新・挿入に権限を絞ります。
Notion SDKを導入する
今回はNotionが公式で開発しているSDKを利用しました。
SDKをインストールします。
npm install @notionhq/client
Notion APIでページを作成する
先ほど設定したトークンを使って疎通部分のコードを作成していきます。
const { Client, LogLevel } = require("@notionhq/client")
// 先ほど作成したトークンを入力してください。↓
const client = new Client({ auth: 'secret_xxxxxxxxxxxxx', logLevel: LogLevel.DEBUG, })
const fs = require('fs');
const csv = require('csv');
CSVを読み込み、それぞれのデータをJson形式に変換した上でAPIを投げます。
ページ作成時のJsonの形式については、以下のリファレンスを参照してください。
;(async () => {
fs.createReadStream(__dirname + '/issues.csv')
.pipe(csv.parse(function(err, arr) {
arr.forEach(data => {
// CSVのデータを読み込み、カラムをそれぞれ変数に突っ込みます。
const title = data[2].toString();
const status = data[1].toString();
const body = data[5];
const url = data[3].toString();
const labels = data[4] ? data[4].toString().split('/') : '';
let labelMultiSelects = [];
if(labels){
labelMultiSelects = labels.map(label => ({'name': label}));
}
let comments = [];
const jsonComment = JSON.parse(data[6]);
if(jsonComment) {
comments = jsonComment.map(comment => ({
"object": "block",
"paragraph": {
"rich_text": [
{
"text": {
"content": comment
}
}
]
}}
));
}
// ページのプロパティ部分です。DBを作成する際に事前にプロパティは作成しておく必要あり。
const properties = {
"タイトル": {
"title": [
{
"text": {
"content": title
}
}
]
},
"ステータス": {
"select": {
"name": status
}
},
"ISSUE URL": {
"rich_text": [
{
"text": {
"content": url
}
}
]
},
"ラベル": {
"multi_select": labelMultiSelects
}
}
// ページのサブページです。ここにはissueのdescriptionとコメントを入れます。
const children = [
{
"object": "block",
"paragraph": {
"rich_text": [
{
"text": {
"content": body
}
}
]
}
}
]
const bodyAndComments = children.concat(comments);
const d1 = new Date();
// 連続でポストすると失敗するので2秒おいてます。
while (true) {
const d2 = new Date();
if (d2 - d1 > 2000) {
break;
}
}
client.pages.create({
parent: { database_id: {挿入したいデータベースのIDを入力してください。} },
properties,
children: bodyAndComments
});
})
}));
})()
スプリント・エピックを作成して紐づける
スプリントを1週間の単位にして別DBにレコードとして作成し、それを紐づけた上でEstimateをつかって1週間のプランニングをしています。
日時のタスクはタイムラインビューで一覧でどのissueが走っているかがわかるようになっています。
メリット・デメリット
メリット
issueをさまざまなビューで確認することが容易になった。
特にガントチャートが非常に便利。
基本的にNotionさえ開いていれば、ドキュメントもタスクも一瞥出来るようになっているので、Chromeのタブが渋滞することがなくなった。
MTG時に共有される資料などは、忘れないようにNotionに置くことで「あれ、どこいった?」がなくなった。
スプリントごとに達成率が計算出来、ベロシティが計算可能になる。
Notion上で議論出来ることはissueに紐づけてDiscussionとしておき、メンションしておくことで、非同期コミュニケーションが活性化して時間に依存しなくなった。
デメリット
NotionからGitHubに戻ることが難しい。
PRなど、GitHubに依存している部分はやはりGitHub issueのほうが便利。
Notionの利用方法がやや複雑で、若干学習コストが高い。
DBやページ作成のルールを定めておかないとカオスになる。
issueのdescription、コメントにあるマークダウンは全て消える。
この記事が気に入ったらサポートをしてみませんか?