Notion × Jira を Google Apps Script + Google Data Studio で自動同期&ダッシュボード化
前提・準備
1. Google アカウント
2. Notion の Integration(トークン取得)
3. Jira API トークン/ユーザー情報
4. Google スプレッドシート ✔️
5. Data Studio(Looker Studio) アクセス権 ✔️
-
Notion 連携設定
• Notion の開発者ポータルで Integration を作成
• NOTION_TOKEN(秘密)と、対象のデータベース ID (NOTION_DATABASE_ID) を控えておく
• 対象 DB の共有設定で Integration に読み書き権限を付与 -
Jira 連携設定
• Jira Cloud 管理画面で API トークンを作成
• ベース URL(例 https://your-domain.atlassian.net)、ユーザー名(メールアドレス)、トークンを控える -
Google Apps Script プロジェクト作成
- Google ドライブで 新規 > その他 > Google Apps Script
- 「プロジェクトの設定」からスクリプト プロパティに以下を登録
• NOTION_TOKEN
• NOTION_DATABASE_ID
• JIRA_BASE_URL
• JIRA_USER
• JIRA_API_TOKEN
⸻
実装ステップ
ステップ1:Notion API 取得
const NOTION_TOKEN = PropertiesService.getScriptProperties().getProperty('NOTION_TOKEN');
const NOTION_DATABASE_ID = PropertiesService.getScriptProperties().getProperty('NOTION_DATABASE_ID');
function fetchNotionTasks(cursor = null) {
const url = `https://api.notion.com/v1/databases/${NOTION_DATABASE_ID}/query`;
const headers = {
"Authorization": `Bearer ${NOTION_TOKEN}`,
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
};
const payload = { page_size: 100 };
if (cursor) payload.start_cursor = cursor;
const res = UrlFetchApp.fetch(url, {
method: 'post',
headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const json = JSON.parse(res.getContentText());
// 結果を統一フォーマットにマッピング
const items = json.results.map(page => ({
source : 'notion',
id : page.id,
title : page.properties.Name.title[0]?.plain_text || '',
status : page.properties.Status.select?.name || '',
assignee : page.properties.Assignee.people[0]?.name || '',
dueDate : page.properties.Due.date?.start || '',
url : page.url
}));
return { items, nextCursor: json.has_more ? json.next_cursor : null };
}
ステップ2:Jira API 取得
const JIRA_BASE_URL = PropertiesService.getScriptProperties().getProperty('JIRA_BASE_URL');
const JIRA_USER = PropertiesService.getScriptProperties().getProperty('JIRA_USER');
const JIRA_API_TOKEN= PropertiesService.getScriptProperties().getProperty('JIRA_API_TOKEN');
function fetchJiraIssues(startAt = 0) {
const jql = encodeURIComponent('project = ABC AND status in ("To Do","In Progress","Done")');
const url = `${JIRA_BASE_URL}/rest/api/2/search?jql=${jql}&startAt=${startAt}&maxResults=50`;
const auth = Utilities.base64Encode(`${JIRA_USER}:${JIRA_API_TOKEN}`);
const res = UrlFetchApp.fetch(url, {
headers: {
Authorization: `Basic ${auth}`,
Accept : 'application/json'
},
muteHttpExceptions: true
});
const data = JSON.parse(res.getContentText());
const items = data.issues.map(issue => ({
source : 'jira',
id : issue.id,
title : issue.fields.summary,
status : issue.fields.status.name,
assignee : issue.fields.assignee?.displayName || '',
dueDate : issue.fields.duedate || '',
url : `${JIRA_BASE_URL}/browse/${issue.key}`
}));
return { items, nextStart: data.startAt + data.maxResults < data.total ? data.startAt + data.maxResults : null };
}
ステップ3:Google スプレッドシートへアップサート
function upsertToSheet(allItems) {
const ss = SpreadsheetApp.openById('YOUR_SHEET_ID');
const sheet = ss.getSheetByName('Tasks');
const data = sheet.getDataRange().getValues();
// ヘッダー行を読む
const headers = data.shift();
const keyCol = headers.indexOf('Source') + 1;
const idCol = headers.indexOf('ID') + 1;
allItems.forEach(item => {
// キー「Source+ID」で既存行を検索
const finder = sheet.createTextFinder(item.source + item.id).findNext();
const rowIdx = finder ? finder.getRow() : sheet.getLastRow() + 1;
const rowValues = [
item.source + item.id,
item.source,
item.id,
item.title,
item.status,
item.assignee,
item.dueDate,
item.url
];
sheet.getRange(rowIdx, 1, 1, rowValues.length).setValues([rowValues]);
});
}
ステップ4:全体をまとめて定期実行
function syncTasks() {
// 1. Notion 全件取得
let notionAll = [], cursor = null;
do {
const res = fetchNotionTasks(cursor);
notionAll = notionAll.concat(res.items);
cursor = res.nextCursor;
} while (cursor);
// 2. Jira 全件取得
let jiraAll = [], startAt = 0;
do {
const res = fetchJiraIssues(startAt);
jiraAll = jiraAll.concat(res.items);
startAt = res.nextStart;
} while (startAt);
// 3. シートにアップサート
upsertToSheet(notionAll.concat(jiraAll));
}
• GAS の「トリガー」メニューから 時間主導型トリガー(例:毎時)を設定してください。
⸻
Google Data Studio ダッシュボード
1. データソース:対象スプレッドシートを追加
2. レポート作成
• ステータス別件数:棒グラフ
• 担当者別残件数:円グラフ
• 期日超過タスク:フィルタ付きテーブル
• 日次タスク消化数推移:折れ線グラフ(シートに日付列を追加推奨)
3. 自動更新
• Data Studio の「データの更新」設定で、GAS 側同期とのタイミングを調整
⸻
まとめ
• 完全カスタムなので、必要なプロパティだけを取捨選択できる
• スクリプトとシート構成を一度作れば、ほぼメンテナンスフリー
• Data Studio でビジュアルを自由に拡張可能
まずは上記コードをコピペで動かし、PoC を進めてみてください。
ご質問や改善アイデアはぜひコメントでお知らせください!
Discussion