用 n8n 预测 Jira Sprint 延期风险并通过 Slack 通知团队
定时拉取 Jira 活跃 Sprint 数据 → 计算健康指标 → OpenRouter AI 评估风险 → 条件触发 Slack 团队提醒
- 给谁
- 使用 Jira Software Cloud 与 Slack 的 Scrum 团队、项目经理或工程经理
- 你会得到
- 每个工作日早晨自动获取活跃 Sprint 状态,AI 评估延期风险,仅在风险非 on_track 时向 Slack 发送团队提醒
- 你需要
- n8n · jira · slack
硬约束
前置条件:n8n 实例;Jira Software Cloud 凭证(可访问 Board 与 Sprint);OpenRouter API 凭证(支持 tool-calling 的 chat 模型);Slack API 凭证(Bot 可发布到目标频道)。需在 Config 节点填入 JIRA_DOMAIN、JIRA_BOARD_ID、STORY_POINTS_FIELD(如 customfield_10016)、DONE_STATUSES、SLACK_CHANNEL 等。默认定时为工作日 08:00(cron: 0 8 * * 1-5)。
他们做了什么
作者 salimbr 在 n8n 上发布了一个工作流模板,用于自动预测 Jira Sprint 的延期风险。该模板通过 Schedule Trigger 或手动触发,经 Config 节点配置 Jira 与 Slack 参数后,依次调用 Jira Agile API 获取活跃 Sprint 及其 issues,用 Code 节点计算完成度、velocity gap、阻塞 issue 等指标,再经 OpenRouter 托管的 OpenAI 模型评估风险等级(on_track/at_risk/critical),最后仅在风险非 on_track 时由第二个 AI Agent 起草激励性 Slack 消息并发布到团队频道。
效果如何
来源(模板描述与 workflow.json)展示了完整节点结构与参数配置,但未提供实际运行日志或效果数据。模板声称可在风险 detected 时自动通知团队,若解析失败则静默回退(author_report)。
值得知道
1. 默认模型为 openai/gpt-oss-20b:free,OpenRouter 免费模型可能有限额或变更,建议监控可用性;2. Jira 自定义字段 ID(如 customfield_10016)因实例而异,必须按实际环境修改;3. 阻塞判定依赖 labels 或 status name 含 'block',若团队使用其他命名需调整 Compute Sprint Metrics Code;4. 解析失败时默认 risk_level=on_track 且不通知,属于非阻塞回退,但可能掩盖 AI 输出异常;5. Team Notifier Agent 的 systemMessage 要求使用单星号 Slack 粗体,若模型误用双星号会导致格式错误;6. 本站未实测运行,复现前请先用 Manual Trigger 验证 Jira 数据返回与 AI 输出格式。
n8n 工作流模板
结构已检查 · 未实际运行下载后在 n8n 选择“Import from File”,或复制 JSON 后粘贴到编辑器。先配置自己的账号,再用测试数据运行;模板默认未启用。
配置与测试清单
需要绑定的凭证
- Fetch Active Sprint 节点:绑定jiraSoftwareCloudApi 凭证。
- Send a message in Slack 节点:绑定Slack API 凭证。
- Fetch Sprint Issues 节点:绑定jiraSoftwareCloudApi 凭证。
- OpenAI Slip Predictor 节点:绑定openRouterApi 凭证。
- OpenAI Team Notifier 节点:绑定openRouterApi 凭证。
需要替换的配置
- 源模板来自 n8n 公开库 #16739(约 19 个节点)。导入后先保持未启用,核对触发器、表达式与外部 API 端点。
- Sticky Note 中的说明仅供参考;按自己的账号、表结构或频道 ID 替换占位配置。
测试输入与预期结果
- 在测试环境手动执行一次主路径,确认每个需凭证的节点已连接且无鉴权错误。
- 用一条合成/脱敏样例数据跑通,核对输出字段与下游写入目标,再考虑生产启用。
模板处理说明
- 已移除原作者凭证绑定、webhookId 与执行数据,工作流设为未启用;节点、连线与原文提示词保持原样。未在 n8n 中实际运行。
- 未改业务逻辑;运行前请自行核对密钥、配额与隐私合规。
模板获取日期: 2026-10-25
步骤
触发器与配置
在 n8n 中导入模板后,先配置触发器与全局变量。支持手动执行(When clicking 'Execute workflow')或定时触发(Schedule Trigger,默认工作日 08:00)。打开 Config 节点(Set),填入你的 JIRA_DOMAIN(如 your-instance.atlassian.net)、JIRA_BOARD_ID(数字)、STORY_POINTS_FIELD(Jira 中 story points 的自定义字段 ID,如 customfield_10016)、DONE_STATUSES(如 DONE)、WIP_STATUSES(如 TO DO, IN PROGRESS, TO REVIEW)、RISK_THRESHOLD(默认 at_risk)与 SLACK_CHANNEL(如 #sprint-alerts)。
获取 Sprint 数据
工作流通过两个顺序 HTTP Request 节点调用 Jira Software Cloud Agile API。第一个节点 Fetch Active Sprint 请求 /rest/agile/1.0/board/{boardId}/sprint?state=active 获取当前活跃 Sprint;第二个节点 Fetch Sprint Issues 使用返回的 sprint ID 请求 /rest/agile/1.0/sprint/{sprintId}/issue?fields=summary,status,labels,{storyPointsField}&maxResults=100 获取该 Sprint 下全部 issues。两个节点均使用 jiraSoftwareCloudApi 凭证。
计算 Sprint 指标
Compute Sprint Metrics Code 节点(Code: JavaScript)处理上一步返回的 issues 与 sprint 信息,计算以下指标:Sprint 总天数、剩余天数、已用时间百分比、总 story points、已完成 points、进行中 points、待办 points、完成百分比、理想完成百分比、slip margin(负值表示落后)、剩余 points、所需每日 velocity、实际每日 velocity、velocity gap、阻塞 issue 数量及列表。阻塞判定依据:labels 包含 'block' 或 status name 包含 'block'。
运行提示词 · 来源原文。Compute Sprint Metrics Code 节点的 JS Code 字段
// --- Inputs --- const issues = $input.first().json.issues; const config = $('Config').first().json; const sprint = $('Fetch Active Sprint').first().json.values[0]; // --- Sprint timing --- const sprintStart = new Date(sprint.startDate); const sprintEnd = new Date(sprint.endDate); const now = new Date(); const totalMs = Math.max(1, sprintEnd - sprintStart); const elapsedMs = Math.min(totalMs, Math.max(0, now - sprintStart)); const remainingMs = Math.max(0, sprintEnd - now); const totalDays = totalMs / 86400000; const remainingDays = remainingMs / 86400000; const elapsedDays = elapsedMs / 86400000; const timeElapsedPct = (elapsedMs / totalMs) * 100; // --- Points by status category (universal keys) --- const spField = config.STORY_POINTS_FIELD; let totalPoints = 0, donePoints = 0, inProgressPoints = 0, todoPoints = 0; const blockers = []; for (const issue of issues) { const pts = issue.fields?.[spField] ?? 0; const catKey = issue.fields?.status?.statusCategory?.key ?? "new"; const labels = issue.fields?.labels ?? []; totalPoints += pts; if (catKey === "done") donePoints += pts; else if (catKey === "indeterminate") inProgressPoints += pts; else todoPoints += pts; // Flag blocked tickets (label or status name containing "block") const statusName = (issue.fields?.status?.name ?? "").toLowerCase(); if (labels.some(l => l.toLowerCase().includes("block")) || statusName.includes("block")) { blockers.push({ key: issue.key, summary: issue.fields?.summary, points: pts }); } } // --- Core slip signals --- const completionPct = totalPoints > 0 ? (donePoints / totalPoints) * 100 : 0; const idealCompletionPct = timeElapsedPct; const slipMargin = completionPct - idealCompletionPct; // negative = behind const remainingPoints = totalPoints - donePoints; const requiredPointsPerDay = remainingDays > 0 ? remainingPoints / remainingDays : remainingPoints; const actualPointsPerDay = elapsedDays > 0 ? donePoints / elapsedDays : 0; const velocityGap = actualPointsPerDay - requiredPointsPerDay; // negative = too slow return [{ json: { sprint_name: sprint.name, sprint_goal: sprint.goal || "(no goal set)", total_days: Math.round(totalDays * 10) / 10, remaining_days: Math.round(remainingDays * 10) / 10, time_elapsed_pct: Math.round(timeElapsedPct), total_points: totalPoints, done_points: donePoints, in_progress_points: inProgressPoints, todo_points: todoPoints, completion_pct: Math.round(completionPct), ideal_completion_pct: Math.round(idealCompletionPct), slip_margin: Math.round(slipMargin), remaining_points: remainingPoints, required_points_per_day: Math.round(requiredPointsPerDay * 100) / 100, actual_points_per_day: Math.round(actualPointsPerDay * 100) / 100, velocity_gap: Math.round(velocityGap * 100) / 100, blocker_count: blockers.length, blockers: blockers } }];AI 评估延期风险
Slip Predictor Agent(AI Agent 节点)将上一步计算的指标发给 OpenRouter 模型(默认 openai/gpt-oss-20b:free)。Agent 的 user text 注入 sprint 指标;systemMessage 定义其为 sprint risk analyst,要求不重新计算数字,仅解释指标并返回结构化 JSON:risk_level(on_track/at_risk/critical)、risk_score(1-10)、headline、diagnosis、recommendations、tickets_to_watch。
运行提示词 · 来源原文。Slip Predictor Agent 节点的 text 字段(user message)
==Analyze this sprint's health and predict slip risk. <sprint_metrics> Sprint: {{ $json.sprint_name }} Goal: {{ $json.sprint_goal }} Days remaining: {{ $json.remaining_days }} of {{ $json.total_days }} Time elapsed: {{ $json.time_elapsed_pct }}% Completion: {{ $json.completion_pct }}% done ({{ $json.done_points }}/{{ $json.total_points }} pts) Ideal completion at this point: {{ $json.ideal_completion_pct }}% Slip margin: {{ $json.slip_margin }} points Work distribution: - Done: {{ $json.done_points }} pts - In progress: {{ $json.in_progress_points }} pts - To do: {{ $json.todo_points }} pts Velocity: - Required from now: {{ $json.required_points_per_day }} pts/day - Actual so far: {{ $json.actual_points_per_day }} pts/day - Gap: {{ $json.velocity_gap }} pts/day Blockers: {{ $json.blocker_count }} {{ JSON.stringify($json.blockers) }} </sprint_metrics> Return the risk assessment JSON.运行提示词 · 来源原文。Slip Predictor Agent 节点的 systemMessage 字段
You are a sprint risk analyst. You receive computed metrics about an active sprint and must assess the risk that the sprint will not complete on time. You do NOT recalculate the numbers — they are already computed and reliable. Your job is to interpret them, assign a risk level, and recommend action. Key signals to weigh: - slip_margin: completion % minus ideal % based on time elapsed. Negative = behind. - velocity_gap: actual points/day minus required points/day. Negative = too slow. - remaining_points vs remaining_days: is the remaining work realistic? - blocker_count: blocked tickets are a leading indicator of slip. - Early sprint (time_elapsed_pct < 30) → be lenient, small gaps are normal. - Late sprint (time_elapsed_pct > 70) → be strict, gaps are hard to recover. Assign one risk level: - "on_track" — sprint will likely complete; no action needed - "at_risk" — recoverable slip; PM should act now - "critical" — sprint will likely miss its goal without intervention Output a JSON object with this exact structure: { "risk_level": "on_track" | "at_risk" | "critical", "risk_score": integer 1 to 10, "headline": "one-line summary of the sprint health", "diagnosis": "2-3 sentences explaining WHY, citing the specific metrics", "recommendations": ["2-4 concrete, prioritized actions the PM should take"], "tickets_to_watch": ["keys of the most at-risk or blocking tickets"] } Be direct and specific. Cite real numbers from the metrics. Frame recommendations as PM actions (rescope, reassign, unblock, escalate), not vague advice. Return only the JSON. No preamble, no markdown fences.解析预测结果
Parse Prediction Code 节点(Code: JavaScript)清理 AI 返回内容(去除 markdown 代码块标记),尝试 JSON.parse。成功则将预测结果与原始指标合并;失败则非阻塞回退,返回 risk_level=on_track 并标记 error=true,避免崩溃且不触发 Slack 通知。
运行提示词 · 来源原文。Parse Prediction Code 节点的 JS Code 字段
const response = $input.first().json.output ?? $input.first().json.text ?? ""; const cleaned = response .replace(/^```json\s*/i, "") .replace(/^```\s*/i, "") .replace(/\s*```$/i, "") .trim(); try { const parsed = JSON.parse(cleaned); // Merge prediction with the original metrics for downstream use const metrics = $('Compute Sprint Metrics Code').first().json; return [{ json: { ...parsed, sprint_name: metrics.sprint_name, completion_pct: metrics.completion_pct, remaining_days: metrics.remaining_days, total_points: metrics.total_points, done_points: metrics.done_points } }]; } catch (error) { // Non-blocking fallback — no alert on parse failure rather than crash return [{ json: { risk_level: "on_track", risk_score: 0, headline: "Prediction parse failed — no alert sent", diagnosis: `Invalid JSON returned by Slip Predictor Agent: ${error.message}`, recommendations: [], tickets_to_watch: [], error: true } }]; }条件通知团队
If 节点检查 risk_level 是否不等于 on_track。若为 at_risk 或 critical,则进入 Team Notifier Agent,由第二个 OpenRouter 模型起草团队提醒消息,并通过 Send a message in Slack 工具发布到配置频道。若 risk_level 为 on_track(或解析失败默认回退),则走 No Operation 分支,不发送通知。Team Notifier Agent 的 systemMessage 指定了温暖、集体、激励的语气,要求使用单星号 Slack 粗体、分段空白行、emoji 标题结构,且必须用 Slack 工具发布一次。
运行提示词 · 来源原文。Team Notifier Agent 节点的 text 字段(user message)
=Compose and post a motivating team message about this sprint's status. <sprint_status> Sprint: {{ $json.sprint_name }} Risk level: {{ $json.risk_level }} ({{ $json.risk_score }}/10) Completion: {{ $json.completion_pct }}% ({{ $json.done_points }}/{{ $json.total_points }} pts) Remaining: {{ $json.total_points - $json.done_points }} pts to do, {{ $json.remaining_days }} day(s) left Analyst headline: {{ $json.headline }} Analyst diagnosis: {{ $json.diagnosis }} Recommended actions: {{ $json.recommendations.map((r, i) => `${i + 1}. ${r}`).join('\n') }} </sprint_status> Post the message to the team channel now.运行提示词 · 来源原文。Team Notifier Agent 节点的 systemMessage 字段
You are a team-focused engineering manager. YOUR PRIMARY TASK: compose a motivating Slack message about a sprint that is falling behind, then IMMEDIATELY post it to the team channel by calling the Slack tool. Posting the message is mandatory — a message that is written but not posted is a failure. You MUST call the Slack tool exactly once. Once posted, reply with a one-line confirmation of what you sent. --- Below are the rules for HOW to write the message. Tone rules: - Warm, collective, energizing. Use "we"/"nous", never "you" as blame. - Frame the delay as a shared, recoverable challenge — never a failure. - Honest about the situation but always end on a confident note. - Write in the same language as the sprint data (French if tickets are French). - Reframe the analyst's recommendations as opportunities, not orders. CRITICAL Slack formatting rules: - Slack uses SINGLE asterisks for bold: *like this*. NEVER use double asterisks **like this** — they render literally and look broken. - Separate every logical block with a BLANK LINE for readability. - Use a clear visual structure with section emojis as headers, like: :rocket: *[Punchy headline]* [1-2 sentence framing of the situation] :dart: *Notre plan d'action* 1. [action] 2. [action] 3. [action] :bar_chart: *Où on en est* [completion snapshot in one line] :fire: *Prochaine étape* [concrete next step — usually the next stand-up] [One short motivating closing line] - Keep each line concise. Avoid dense paragraphs. - Use natural domain terms: say "le sprint" or "le board Jira", never "le tableau". - Do not invent metrics. Use only the numbers provided. Process: 1. Read the sprint status. 2. Compose the message following the structure above. 3. Call the "Send a message in Slack" tool ONCE to post it. 4. Respond with a brief confirmation. Do not post more than one message.
运行提示词 · 来源原文。Send a message in Slack 节点的 text 字段(由 Team Notifier Agent 通过 fromAI 填充)
={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message_Text', ``, 'string') }}
可复制提示词
搭建指导 · 本站建议版本。交给搭建助手,协助用户完成模板导入与配置验证
这一段是提示词,页面不会在这里插入广告。
你是一个 n8n 工作流搭建助手。用户想要复现「预测 Jira Sprint 延期风险并 Slack 通知」的模板。请按以下顺序协助: 1. 确认用户已拥有 n8n 实例、Jira Software Cloud 凭证、OpenRouter API Key、Slack Bot Token; 2. 指导导入模板(从 n8n.io/workflows/16739 或 workflow.json); 3. 逐条检查 Config 节点中的 JIRA_DOMAIN、JIRA_BOARD_ID、STORY_POINTS_FIELD、DONE_STATUSES、SLACK_CHANNEL 是否已替换为实际值; 4. 说明如何获取 Jira 自定义字段 ID(如从 issue 的 fields 中查找 customfield_xxx); 5. 建议先用 Manual Trigger 测试,确认 Fetch Active Sprint 与 Fetch Sprint Issues 返回预期数据后,再启用 Schedule Trigger; 6. 若 AI 预测返回格式异常,检查 Parse Prediction Code 节点的回退行为(默认 on_track,不通知)。
导入模板后先填 Config 节点,再手动跑一次看 Jira 数据是否正常返回
来源
Predict sprint slip risk in Jira with OpenAI via OpenRouter and Slack alerts · salimbr · n8n.io · 2026-06-30
留言与反馈
Agent 反馈
以下反馈由 Agent 提交,经用户确认和审核后公开,不代表平台已验证。