ãBacklogã®ã¿ã¹ã¯ãæ°ã¥ãããæéãéããŠæŸçœ®ãããŠããâŠã
ãããŒã ããšã«éç¥å
ãåãããããã©ãèšå®ãé£ãããâŠã
ãããªæ©ã¿ã解決ããããã«ãGoogle Apps ScriptïŒGASïŒã䜿ã£ãŠãæéåã課é¡ãSlackãžèªåéç¥ããçµæãã¹ãã¬ããã·ãŒãã«èšé²ãããèªåããããŒã«ã·ã¹ãã ããäœã£ãŠã¿ãŸããïŒ
ããã°ã©ãã³ã°æªçµéšã®æ¹ã§ãããã®èšäºã®éãã«äœæ¥ããã°30åã§å°å
¥å¯èœã§ãïŒ
äºåæºå
ããã°ã©ã ãSlackã«ã¡ãã»ãŒãžãå±ããããã«ã¯ãIncoming Webhookãšãã飿ºçšã®URLãçºè¡ããå¿ èŠããããŸãã
- ãã©ãŠã¶ã§ Slack App ãã£ã¬ã¯ããªïŒIncoming WebhooksïŒ ã«ã¢ã¯ã»ã¹
- ãSlackã«è¿œå ããã¯ãªãã¯
- ããã£ã³ãã«ãžã®æçš¿ããšããé ç®ãããã®ã§ãéç¥ãé£ã°ãããSlackã®ãã£ã³ãã«ãéžæ
- [Incoming Webhook ã€ã³ãã°ã¬ãŒã·ã§ã³ã®è¿œå ] ãšããç·è²ã®ãã¿ã³ãã¯ãªãã¯
- Webhook URLãã³ããŒãã
â»ãã®URLã¯ãSTEP 3ãã§äœ¿ãã®ã§ãã¡ã¢åž³ãªã©ã«è²Œãä»ããŠãããŠãã ããã
â»éç¥å
ãåãããå Žåã¯ããã®æé ãå床å®è¡ãã
å°å ¥æé
以äžã®4ã¹ãããã§é²ããŸãã
- ã¹ãã¬ããã·ãŒãã®æºåïŒããããŒã«çµæãèšé²ããå°åž³ïŒãã°ã·ãŒãïŒãäœæ
- ã¹ã¯ãªããã®ã»ããã¢ããïŒããŒã«ã®é è³ãšãªãããã°ã©ã ã貌ãä»ã
- ã¹ã¯ãªããããããã£ã®èšå®ïŒWebhook URLãAPIããŒãå®å šãªå Žæã«ä¿ç®¡
- ãã¹ãå®è¡ãšæ¿èªïŒæåã§ããã°ã©ã ãåãããGoogleã®æ¿èªãæžãŸãã
- èªååïŒããªã¬ãŒïŒã®èšå®ïŒæ±ºãŸã£ãæéã«åæã«åãããã«èšå®
STEP 1ïŒã¹ãã¬ããã·ãŒãã®æºå
ãŸãã¯ãããããŒã«ã®çµæãèšé²ããããã®ã·ãŒããçšæããŸãã
- æ°ããGoogleã¹ãã¬ããã·ãŒããäœæãã
- ç»é¢äžã®ã·ãŒãåïŒã¿ãåïŒãããã«ã¯ãªãã¯ããŠãéä¿¡ãã°ãã«å€æŽ
泚æïŒ 挢åãã¹ããŒã¹ãéããšæ£ããèšé²ãããªãã®ã§æ£ç¢ºã«å ¥åããŠãã ããïŒ
STEP 2ïŒã¹ã¯ãªããã®è²Œãä»ã
次ã«ãããã°ã©ã ïŒã³ãŒãïŒãã³ããŒïŒããŒã¹ãããŸãã
- ã¹ãã¬ããã·ãŒãã®ã¡ãã¥ãŒãã [æ¡åŒµæ©èœ] > [Apps Script] ãéã
- æåããæžããŠãã
function myFunction() {...}ããã¹ãŠæ¶ããŠã以äžã®å®æçã³ãŒãããã®ãŸãŸè²Œãä»ãã
ãã³ããçšã宿çã³ãŒã
/**
* Backlog æéè¶
é課é¡ããããŒã«éç¥ã¹ã¯ãªãã
*/
function notifyTeamActiveTasks() {
const now = new Date();
const dayOfWeek = now.getDay();
// --- ã1. ææ¥ãªããã¿ãŒãæ(1)ãšæš(4)ã®ã¿å®è¡ ---
if (dayOfWeek !== 1 && dayOfWeek !== 4) {
console.log("æ¬æ¥ã¯å®è¡å¯Ÿè±¡æ¥ã§ã¯ãããŸããã");
return;
}
// --- ã2. èšå®æ
å ±ã®èªã¿èŸŒã¿ã ---
const props = PropertiesService.getScriptProperties();
const BACKLOG_API_KEY = props.getProperty('BACKLOG_API_KEY');
const DEFAULT_WEBHOOK = props.getProperty('DEFAULT_WEBHOOK');
const BACKLOG_SPACE_ID = 'ããªãã®ã¹ããŒã¹ID'; // äŸ: example-space
const BACKLOG_DOMAIN = 'backlog.com';
const TARGET_PROJECT_KEYS = ['PROJ_KEY']; // äŸ: PRJ_ABC
// --- ã3. ã¡ã³ããŒãªã¹ãã ---
const USER_LIST = {
// å¥ã®ãã£ã³ãã«ã«é£ã°ããã人ã ã propKey ãæžã
'ããŒã Aæ
åœè
': { id: 'U11111111', propKey: 'WEBHOOK_TEAM_A' },
// ããã©ã«ãïŒå
±éïŒã®ãã£ã³ãã«ã§è¯ã人ã¯ååãšIDã ãã§OKïŒ
'äžè¬æ
åœè
1': { id: 'U22222222' },
'äžè¬æ
åœè
2': { id: 'U33333333' }
};
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const todayStr = Utilities.formatDate(today, "JST", "yyyy-MM-dd");
const oneWeekAgo = new Date(today.getTime());
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
let finalExportData = [];
let channelBuckets = {};
// --- ã4. 課é¡ããŒã¿ã®ååŸãšå€å®ã ---
TARGET_PROJECT_KEYS.forEach(projectKey => {
try {
const baseUrl = `https://${BACKLOG_SPACE_ID}.${BACKLOG_DOMAIN}/api/v2`;
const projectRes = UrlFetchApp.fetch(`${baseUrl}/projects/${projectKey.trim()}?apiKey=${BACKLOG_API_KEY}`, { "muteHttpExceptions": true });
if (projectRes.getResponseCode() !== 200) return;
const projectId = JSON.parse(projectRes.getContentText()).id;
let allIssues = [];
for (let offset = 0; offset < 400; offset += 100) {
const issuesUrl = `${baseUrl}/issues?apiKey=${BACKLOG_API_KEY}&projectId[]=${projectId}&parentChild=0&count=100&offset=${offset}`;
const fetched = JSON.parse(UrlFetchApp.fetch(issuesUrl).getContentText());
if (!fetched || fetched.length === 0) break;
allIssues = allIssues.concat(fetched);
}
for (const [backlogName, info] of Object.entries(USER_LIST)) {
const targetIssues = allIssues.filter(issue => {
if (!issue.dueDate || issue.status.name.includes("å®äº")) return false;
const d = new Date(issue.dueDate); d.setHours(0,0,0,0);
if (d > today) return false;
const isDirect = issue.assignee && issue.assignee.name === backlogName;
let isChild = false;
if (issue.parentIssueId) {
const p = allIssues.find(i => i.id === issue.parentIssueId);
if (p && p.assignee && p.assignee.name === backlogName) isChild = true;
}
return isDirect || isChild;
});
if (targetIssues.length > 0) {
finalExportData.push([now, backlogName, projectKey, targetIssues.length]);
// â
ä¿®æ£ãã€ã³ãïŒpropKeyããªããã°DEFAULT_WEBHOOKã䜿çš
const targetWebhook = (info.propKey ? props.getProperty(info.propKey) : null) || DEFAULT_WEBHOOK;
if (!channelBuckets[targetWebhook]) channelBuckets[targetWebhook] = "";
let memberMsg = `\n\nð <@${info.id}> ãã ã${projectKey}ã\n`;
targetIssues.forEach(issue => {
const dateStr = issue.dueDate.split('T')[0];
const d = new Date(issue.dueDate); d.setHours(0,0,0,0);
let statusMark = (dateStr === todayStr) ? "ðš " : (d >= oneWeekAgo) ? "â ïž " : "ð¥ ";
memberMsg += `${issue.parentIssueId ? " â " : "ã»"}${statusMark} (${issue.status.name} / æé: ${dateStr})\n`;
});
channelBuckets[targetWebhook] += memberMsg;
}
}
} catch (e) { console.error("Error: " + e.message); }
});
// --- ã5. Slackéä¿¡ãšãã°èšé²ã ---
for (const [url, msg] of Object.entries(channelBuckets)) {
UrlFetchApp.fetch(url, { "method": "post", "contentType": "application/json", "payload": JSON.stringify({ "text": "ð¢ *BacklogæéããããŒã«ã¬ããŒã*\n" + msg }) });
}
writeToSheetNow(finalExportData);
}
function writeToSheetNow(dataRows) {
if (!dataRows || dataRows.length === 0) return;
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName('éä¿¡ãã°') || ss.insertSheet('éä¿¡ãã°');
if (sheet.getLastRow() === 0) sheet.appendRow(['éä¿¡æ¥æ', 'æ
åœè
å', 'ãããžã§ã¯ã', '課é¡ä»¶æ°']);
sheet.getRange(sheet.getLastRow() + 1, 1, dataRows.length, dataRows[0].length).setValues(dataRows);
} ã³ãŒãã®è§£èª¬ïŒäœãããŠããã®ïŒ
ã1. ææ¥ãªããã¿ãŒã
ããã°ã©ã ãèµ·åããçŽåŸã«ä»æ¥ã¯äœææ¥ãïŒã確èªããææ(1)ãšæšæ(4)以å€ã§ããã°ãããã§åŠçãäžæããŸãã
èšå®ïŒ
dayOfWeek !== 1 && dayOfWeek !== 4 ã®æ°åãæžãæããŸãïŒ1=æã4=æšïŒããã¹ãã§ä»ããåããããå Žåã¯ããã® if æãäžæçã« // ã§ã³ã¡ã³ãã¢ãŠãããŠãã ãããæ¯æ¥åããããå Žåã¯ããã®ãããã¯ããšåé€ãŸãã¯ã³ã¡ã³ãã¢ãŠãããŸãã
ã2. èšå®æ
å ±ã®èªã¿èŸŒã¿ã
ã¹ã¯ãªããããããã£ã«ä¿åããAPIããŒããBacklogã®ã¹ããŒã¹URLãªã©ãèªã¿èŸŒã¿ãŸããæ©å¯æ
å ±ãã³ãŒãã«çŽæ¥æžããªãããšã§ãã»ãã¥ãªãã£ãæ
ä¿ããŠããŸãã
ïŒã³ãŒãå ã«ã¯èšèŒãããã¹ã¯ãªããããããã£ããèªã¿èŸŒãã§ãããã®ïŒâ»STEP3ã§èšå®æ¹æ³ã説æããŠããŸã
BACKLOG_API_KEYïŒBacklogã®ãµãŒããŒã誰ãã¢ã¯ã»ã¹ããŠããããèå¥ããããŒã¿ã®ååŸãèš±å¯ããããã®èªèšŒæ å ±ã§ããDEFAULT_WEBHOOKïŒSlackã®ç¹å®ã®ãã£ã³ãã«ã«ã¡ãã»ãŒãžãå±ããããã®éä¿¡å URLã§ãã
â APIããŒãURLã¯ãéè¡ã®æèšŒçªå·ã®ãããªãã®ã§ãïŒã³ãŒãå ã«çŽæ¥æžããšã誰ãã«ã³ãŒããèŠãããéã«æªçšãããæãããããããGASã®ã¹ã¯ãªããããããã£ãšããå®å šãªå Žæã«ä¿ç®¡ããŠãããã°ã©ã ããåŒã³åºã圢ã«ããŠããŸãã
ïŒã³ãŒãå ã«çŽæ¥æå®ãããã®ïŒâ»ããã¯ãããããã®ç°å¢ã«åãããŠæžãæããå¿ èŠãããå Žæã§ãã
BACKLOG_SPACE_IDïŒèªåã®Backlog URLã®https:// ã®çŽåŸãã .backlog.com ã®åãŸã§ã«ããæååïŒäŸïŒhttps://example-space.backlog.com/dashboardã§ããã°ãexample-space ãIDïŒTARGET_PROJECT_KEYSïŒç£èŠããããããžã§ã¯ãã®ããŒãç»é²
ã3. ã¡ã³ããŒãªã¹ãã
Backlogã®æ
åœè
åãšãSlackã®éç¥å
ïŒã¡ã³ããŒIDïŒãçŽä»ããŸãã
- ååïŒBacklogã®è¡šç€ºåãš1æåãéããªãããã«å ¥åããŠãã ããã
- idïŒSlackãããã£ãŒã«ãããã¡ã³ããŒIDãã³ããŒãã§ååŸãã
Uããå§ãŸãè±æ°åãããããªããšã¡ã³ã·ã§ã³ãé£ã³ãŸãããïŒSlackã®ãããã£ãŒã«ã衚瀺 ïŒ [ãã®ä»(âŠ)] ïŒ [ã¡ã³ããŒIDãã³ããŒ]ïŒ - propKeyïŒéä¿¡å
ãã£ã³ãã«ãåããã人ã ããã¹ã¯ãªããããããã£ã«ç»é²ããååïŒäŸ:
WEBHOOK_TEAM_AïŒãæžããŸããå ±éã®ãã£ã³ãã«ã§è¯ã人ã¯ããã®é ç®ãäžžããšåé€ããŠOKã§ãïŒ
ã4. 課é¡ããŒã¿ã®ååŸãšå€å®ã
Backlogããæå€§400ä»¶ã®èª²é¡ãååŸããæéæ¥ãããšã«ã¢ã€ã³ã³ãå²ãåœãŠãŸãã
- ðšïŒæ¬æ¥ãæéïŒåœæ¥ã®å¯Ÿå¿ãå¿ èŠïŒ
- â ïžïŒè¶ é1é±é以å ïŒæ©æ¥ãªå¯Ÿå¿ãæšå¥šïŒ
- ð¥ïŒè¶ é1é±é以äžïŒæåªå ã®ç¢ºèªãå¿ èŠïŒ
ã5. Slackéä¿¡ãšãã°èšé²ã
ãŸãšããã¡ãã»ãŒãžãSlackãžéä¿¡ããã¹ãã¬ããã·ãŒãã®ãéä¿¡ãã°ãã·ãŒãã«ããã€ã誰ã«ãäœä»¶éã£ããã远èšããŸãã
STEP 3ïŒã¹ã¯ãªããããããã£ã®èšå®
- ãšãã£ã¿å·ŠåŽã® [æ¯è»ã¢ã€ã³ã³] ïŒ [ã¹ã¯ãªããããããã£ãç·šé] ãéã
- ååãšå€ãã»ããã§ç»é²ãã
èšå®é ç®äžèЧ
| é ç®å | å®çŸ©ã»åœ¹å² |
| BACKLOG_API_KEY | Backlog APIã«ã¢ã¯ã»ã¹ããããã®å人çšèªèšŒã㌠|
| DEFAULT_WEBHOOK | Slackéç¥ãå®è¡ããããã®Webhook URLïŒãšã³ããã€ã³ãïŒ |
| WEBHOOK_TEAM_A | å¿ èŠã«å¿ããŠå¥Slackãã£ã³ãã«ã®URLã远å |
STEP4ïŒãã¹ãå®è¡ãšæ¿èª
ããªã¬ãŒãèšå®ããåã«ãäžåºŠã ãæåã§åãããŠGoogleã®æ¿èªãæžãŸããå¿ èŠããããŸãããããå¿ãããšãèªåå®è¡ããšã©ãŒã§æ¢ãŸã£ãŠããŸããŸãã
- ãšãã£ã¿äžéšã® [å®è¡] ãã¿ã³ãã¯ãªãã¯
- ãæ¿èªãå¿ èŠã§ãããšãããããã¢ãããåºãã®ã§ã [æš©éã確èª] ãã¯ãªãã¯
- èªåã®Googleã¢ã«ãŠã³ããéžæããèŠåç»é¢ãåºãã [詳现] ïŒ [ïŒãããžã§ã¯ãåïŒã«ç§»åïŒå®å šã§ã¯ãªãïŒ] ãã¯ãªãã¯
- å 容ã確èªããæåŸã« [èš±å¯] ãã¯ãªãã¯
- äžã®ãã°ã«ãå®è¡å®äºããšåºãã°ãGoogleããã®èš±å¯ãå®äºããŠããŸãïŒ
STEP 5ïŒèªååïŒããªã¬ãŒïŒã®èšå®
- ãšãã£ã¿å·ŠåŽã® [æèšã¢ã€ã³ã³ïŒããªã¬ãŒïŒ] ãã¯ãªãã¯
- [ïŒ ããªã¬ãŒã远å ] ãæŒãã以äžã®ããã«èšå®
- 颿°ïŒ
notifyTeamActiveTasks - ãœãŒã¹ïŒæéäž»å°å
- ã¿ã€ãïŒæ¥ä»ããŒã¹ã®ã¿ã€ããŒ
- æå»ïŒåå9æã10æãªã©ïŒéç¥ãããæéïŒ
- â»Google Apps ScriptïŒGASïŒã®ä»æ§äžãããªã¬ãŒã§èšå®ããæå»ã«ååäœã§ãŽã£ããéç¥ãå±ãããã§ã¯ãããŸãããäŸãã°ãåå9æã10æããšèšå®ããå Žåããã®1æéã®æ å ã®ã©ããã§å®è¡ãããŸãã
â»ååå®è¡æã®æ¿èªæé
- ãæ¿èªãå¿ èŠã§ãããšãããããã¢ãããåºãã®ã§ã[æš©éã確èª] ãã¯ãªãã¯
- èªåã®Googleã¢ã«ãŠã³ãã衚瀺ããããã¯ãªãã¯
- ç»é¢å·Šäžã«ãã [詳现] ãšããå°ããªæåãã¯ãªãã¯
- äžã®æ¹ã«è¡šç€ºããã [ïŒãããžã§ã¯ãåïŒã«ç§»åïŒå®å šã§ã¯ãªãïŒ] ãã¯ãªãã¯â»GoogleããèŠåç»é¢ãåºãŸãããèªåã§äœæããã¹ã¯ãªãããªã®ã§å®å šã§ãã
- å 容ã確èªããäžçªäžã® [èš±å¯] ãã¯ãªãã¯
éç¥ã®å®æã€ã¡ãŒãž
å®è¡ããããšãæ åœè ããšã«æŽçãããã¬ããŒããSlackã«å±ããŸããæéã®è¶ éå ·åã«åãããŠã¢ã€ã³ã³ãå€åãããããåªå ãã¹ã課é¡ãäžç®ã§å€æã§ããŸãã

ãããžã§ã¯ãã§æŽ»ããéã®æ³šæãã¢ã€ãã¢
éçšäžã®æ³šæç¹
- APIããŒã®æš©éãšæå¹æéïŒ
BACKLOG_API_KEYã¯ãçºè¡ãããŠãŒã¶ãŒã®æš©éã«äŸåããããããã®ãŠãŒã¶ãŒããããžã§ã¯ãããè±éããããã¢ã«ãŠã³ããç¡å¹åãããããããšãã¹ã¯ãªããã忢ããŠããŸããŸãã - APIã®å®è¡å¶éïŒBacklog APIã«ã¯äžå®æéå ã®ãªã¯ãšã¹ãåæ°å¶éããããŸãããã®ã¹ã¯ãªããã§ã¯æå€§400ä»¶ã®èª²é¡ãååŸããããã«èšèšãããŠããŸãããæ¥µç«¯ã«å·šå€§ãªãããžã§ã¯ãããæ°ååã®ãããžã§ã¯ããäžåºŠã«ã¹ãã£ã³ããå Žåã¯ããšã©ãŒãçºçããå¯èœæ§ããããŸãããã®éã¯å®è¡æéãããããªã©ã®å·¥å€«ãå¿ èŠã§ãã
- æ å ±ã®åæïŒè¡šç€ºåã®äžäžèŽïŒïŒBacklogã®è¡šç€ºåã¯ãååãšååã®éã®ã¹ããŒã¹ã®æç¡ãŸã§å«ããŠå€å®ããŠããŸããéç¥ãå±ããªãå Žåã¯ããŸãBacklogäžã®ååãšã³ãŒãå ã®èšè¿°ãå®å šã«äžèŽããŠãããã確èªããŠãã ããã
ããã«æ¥åãå¹çåããã¢ã€ãã¢
- 鱿¬¡ã¬ããŒããšããŠã®æŽ»çšïŒã¹ãã¬ããã·ãŒãã®éä¿¡ãã°ã«èç©ãããããŒã¿ãã°ã©ãåããã°ãã©ã®ææã«ã¿ã¹ã¯ãæ»çãããããã誰ã«è² è·ãéäžããŠããããå¯èŠåã§ããããŒã ã®çç£æ§åäžã®ããã®åæè³æãšããŠæŽ»çšã§ããŸãã
- ç£èŠå¯Ÿè±¡ïŒBacklogãããžã§ã¯ãïŒãå¢ããïŒ
TARGET_PROJECT_KEYSã®é åã«ãããžã§ã¯ãããŒã远å ããã ãã§ã1床ã®å®è¡ã§è€æ°ã®ãããžã§ã¯ããæšªæçã«ã¹ãã£ã³ã§ããŸãã
ãŸãšã
ä»åã¯ãGoogle Apps ScriptãæŽ»çšããŠBacklogã®æéè¶
é課é¡ãèªåéç¥ããã·ã¹ãã ã®æ§ç¯æ¹æ³ã解説ããŸããã
æ¬ããŒã«ã®å°å
¥ã¡ãªãã
- ã¿ã¹ã¯æŸçœ®ã®ææ¢ïŒ3段éã®ç·æ¥åºŠã¢ã€ã³ã³ã«ãããåªå ãã¹ã課é¡ãäžç®ã§å€å¥å¯èœã«ãªããŸãã
- 管çã³ã¹ãã®åæžïŒæåã®ãªãã€ã³ãäœæ¥ãèªååãããæ åœè ã¯å¥ã®ã¿ã¹ã¯ã«æéãå²ããããã«ãªããŸãã
- 確å®ãªãšããã³ã¹ä¿æïŒã¹ãã¬ããã·ãŒããžã®ãã°èšé²ã«ãããããããŒã«ã®å®æœç¶æ³ãåŸãã客芳çã«æ¯ãè¿ãããšãã§ããŸãã
æåŸã«
ãã®ããããŒã«éç¥ãå§ãŸã£ããããšãã£ãŠãããŒã ã®å
šã¿ã¹ã¯ã綺éºã«çä»ãããã§ã¯ãããŸããã(éç¥ãã¹ã«ãŒãã人ãããããBacklogã®æŽæ°ãå¿ãã人ãããããšâŠ..)
倧åãªã®ã¯ããã®ã·ã¹ãã ãäžåºŠäœã£ãŠçµããã«ãããéçšããªãã埮調æŽãç¹°ãè¿ãããšã§ããäŸãã°ãéç¥ã®æåŸã«èªåãããå±ãŸãã®ã³ã¡ã³ããäžè¡è¶³ãã ãã§ããããŒã ã®åãåãæ¹ã¯å€ãããŸããããããããªæ©èœã远å ããŠãã£ãšèªåãã©ã¯ã«ãããïŒãšããã¢ã€ãã¢ãæµ®ããã ããè¿·ããGeminiã«çžè«ããŠã¿ãŸããããèªåã®æå³ãæ±²ã¿åã£ãã³ãŒãã®ææ¡ããéçšã®ã³ãããã€ã§ãã¢ããã€ã¹ããŠãããŸãã
AIã壿ã¡çžæã«ããªãããããŒã ã«ãšã£ãŠæé«ã®ä»çµã¿ãäœã£ãŠãããŸãããïŒ