ChatGPT软件基础设施AI 评分 · 待评分AI 辅助编辑·未发现明显提示词注入风险n8n.io

n8n 自动工程日报:Jira + GitHub 汇总推 Slack

定时触发 → 并行拉取 Jira 与 GitHub 数据 → OpenAI 生成摘要 → Slack Block Kit 推送 → 可选 Google Sheets 记录

有一点经验步骤流程提示词贴士
给谁
需要向团队推送每日工程进展的工程师、技术负责人或运维人员
你会得到
工作日早晨自动收到含 Jira 动态、GitHub PR 状态与风险项的 Slack 摘要,可选同步到表格
你需要
n8n · google-sheets · github · slack

硬约束

前置条件:n8n 实例;Jira Cloud、GitHub、OpenAI、Slack 凭证;Google Sheets 凭证仅在开启记录时需要。需有 Jira 项目访问权限、GitHub 仓库读取权限(fine-grained token 需 repo + pull-request read)、Slack 频道发帖权限。模板使用 gpt-4o-mini,温度 0.2,maxTokens 2000。

他们做了什么

intuz 在 n8n 模板市场发布了一个工作流模板,展示如何在工作日早晨自动采集 Jira 近 24 小时更新与停滞中的 In Progress 工单,同时并行拉取 GitHub 仓库的近期 PR 动态与未评审的 stale PR,经 OpenAI 生成结构化摘要后,以 Slack Block Kit 格式推送到指定频道,并可选择将结果记录到 Google Sheets。模板提供了完整的 35 节点 workflow JSON、节点说明与配置示例。

效果如何

作者未提供实测运行数据或 ROI。模板页面显示总浏览量 40 次(截至采集时)。n8n API 响应中的 pinData 包含一次 OpenAI 调用示例输出,显示模型返回了符合格式的 JSON 摘要(author_report)。

值得知道

1) 本站未实测该工作流,reproduction=not_tested。2) GitHub 侧使用 review:none 搜索 qualifier 来低成本定位未评审 PR,无需逐 PR 调用。3) OpenAI 输出要求 STRICT JSON,但模板已在 Parse LLM Summary 节点内置 JSON.parse 失败降级处理。4) Google Sheets 列名需用户自行对齐(ok, channel, message, message_timestamp),模板不会自动建表。5) userMap 为可选配置,未映射时 mention 函数回退为显示原始用户名或 'unassigned'。6) 模板默认 repos 数组含示例值,必须替换为实际仓库。

n8n 工作流模板

结构已检查 · 未实际运行

下载后在 n8n 选择“Import from File”,或复制 JSON 后粘贴到编辑器。先配置自己的账号,再用测试数据运行;模板默认未启用。

配置与测试清单

需要绑定的凭证

  • 导入后按各需鉴权节点绑定你自己的凭证;本附件不含原作者密钥。

需要替换的配置

  • 源模板来自 n8n 公开库 #19880(约 35 个节点)。导入后先保持未启用,核对触发器、表达式与外部 API 端点。
  • Sticky Note 中的说明仅供参考;按自己的账号、表结构或频道 ID 替换占位配置。

测试输入与预期结果

  • 在测试环境手动执行一次主路径,确认每个需凭证的节点已连接且无鉴权错误。
  • 用一条合成/脱敏样例数据跑通,核对输出字段与下游写入目标,再考虑生产启用。

模板处理说明

  • 已移除原作者凭证绑定、webhookId 与执行数据,工作流设为未启用;节点、连线与原文提示词保持原样。未在 n8n 中实际运行。
  • 未改业务逻辑;运行前请自行核对密钥、配额与隐私合规。

模板获取日期: 2026-10-25

步骤

  1. 配置触发器与全局参数

    在 n8n 中导入模板后,先配置「When Weekday 9AM」Schedule Trigger:使用 Cron 表达式 0 9 * * 1-5,并设置目标时区。接着打开「Set Config Fields」节点,在 assignments 中填写:jiraBaseUrl(如 https://your-domain.atlassian.net)、jiraProjectKey、staleTicketDays(默认 3)、staleReviewHours(默认 48)、repos(JSON 数组,每项含 owner 与 name)、slackChannel(如 #eng-daily-digest)、sheetEnabled(true/false)、sheetId、sheetTabName、openAiModel(默认 gpt-4o-mini),以及可选的 userMap(Jira accountId / GitHub login → Slack userId 映射)。

  2. 并行采集并归一化 Jira 数据

    工作流从 Config 节点并行分出两条 Jira 分支: 1)「Fetch Jira Recent Activity」使用 JQL:project = {{ $json.jiraProjectKey }} AND updated >= -1d ORDER BY updated DESC,获取全部结果。 2)「Fetch Jira Stale Tasks」使用 JQL:project = {{ $json.jiraProjectKey }} AND status = "In Progress" AND updated <= -{{ $json.staleTicketDays }}d ORDER BY updated ASC。 各自下游的 Code 节点将字段归一化为 {key, summary, status, assignee, assigneeAccountId, updated, url, daysStale?},最终通过「Merge Jira Data」合并为单一 Jira payload。

    运行提示词 · 来源原文。Normalize Jira Recent Data 节点(Code / JavaScript)

    const baseUrl = $('Set Config Fields').item.json.jiraBaseUrl;
    const issues = $input.all().map(item => {
      const issue = item.json;
      return {
        key: issue.key,
        summary: issue.fields?.summary,
        status: issue.fields?.status?.name,
        assignee: issue.fields?.assignee?.displayName || 'Unassigned',
        assigneeAccountId: issue.fields?.assignee?.accountId || null,
        updated: issue.fields?.updated,
        url: `${baseUrl}/browse/${issue.key}`
      };
    });
    return [{ json: { recentIssues: issues } }];

    运行提示词 · 来源原文。Normalize Jira Stale Data 节点(Code / JavaScript)

    const baseUrl = $('Set Config Fields').item.json.jiraBaseUrl;
    const now = Date.now();
    const issues = $input.all().map(item => {
      const issue = item.json;
      const updated = new Date(issue.fields?.updated).getTime();
      const daysStale = Math.floor((now - updated) / 86400000);
      return {
        key: issue.key,
        summary: issue.fields?.summary,
        status: issue.fields?.status?.name,
        assignee: issue.fields?.assignee?.displayName || 'Unassigned',
        assigneeAccountId: issue.fields?.assignee?.accountId || null,
        updated: issue.fields?.updated,
        daysStale,
        url: `${baseUrl}/browse/${issue.key}`
      };
    });
    return [{ json: { staleIssues: issues } }];
  3. 并行采集并归一化 GitHub 数据

    「Split Repository List」Code 节点将 Config.repos 拆分为每个仓库一个 item,并计算 sinceISO(24h 前)与 staleDateISO。随后并行执行: 1)「Fetch GitHub PR Activity」使用原生 GitHub 节点 repository.getPullRequests(state=all, sort=updated, direction=desc),按仓库拉取后由「Flatten GitHub PR Activity」展开数组,再由「Normalize GitHub Recent Data」按 created_at/merged_at/closed_at 与 24h cutoff 分类为 prsOpened / prsMerged / prsClosed。 2)「Fetch Stale GitHub PRs」使用 HTTP Request 调用 GET https://api.github.com/search/issues?q=repo:{owner}/{name} is:pr is:open review:none&per_page=50,经「Flatten GitHub Stale PRs」提取 body.items 后,由「Normalize GitHub Stale Data」按 staleReviewHours 过滤并计算 hoursOpen。 两条线最终通过「Merge GitHub Data」合并。

    运行提示词 · 来源原文。Split Repository List 节点(Code / JavaScript)

    const config = $json;
    const sinceISO = new Date(Date.now() - 24*60*60*1000).toISOString();
    const staleHours = config.staleReviewHours || 48;
    const staleDateISO = new Date(Date.now() - staleHours*60*60*1000).toISOString().split('T')[0];
    return (config.repos || []).map(repo => ({
      json: {
        owner: repo.owner,
        name: repo.name,
        fullName: `${repo.owner}/${repo.name}`,
        sinceISO,
        staleDateISO
      }
    }));

    运行提示词 · 来源原文。Flatten GitHub PR Activity 节点(Code / JavaScript)

    const results = [];
    for (const item of $input.all()) {
      const body = item.json;
      if (Array.isArray(body)) {
        for (const pr of body) {
          if (pr && pr.number != null) results.push({ json: pr });
        }
      } else if (body && body.number != null) {
        results.push({ json: body });
      }
    }
    return results.length ? results : [{ json: { number: null } }];

    运行提示词 · 来源原文。Normalize GitHub Recent Data 节点(Code / JavaScript)

    const now = Date.now();
    const cutoff = now - 24*60*60*1000;
    const prs = $input.all().map(i => i.json).filter(pr => pr && pr.number != null);
    const opened = [], merged = [], closed = [];
    for (const pr of prs) {
      const createdAt = new Date(pr.created_at).getTime();
      const mergedAt = pr.merged_at ? new Date(pr.merged_at).getTime() : null;
      const closedAt = pr.closed_at ? new Date(pr.closed_at).getTime() : null;
      const entry = {
        number: pr.number,
        title: pr.title,
        author: pr.user?.login,
        url: pr.html_url,
        repo: pr.base?.repo?.full_name
      };
      if (createdAt >= cutoff) opened.push(entry);
      if (mergedAt && mergedAt >= cutoff) merged.push(entry);
      if (closedAt && !mergedAt && closedAt >= cutoff) closed.push(entry);
    }
    return [{ json: { prsOpened: opened, prsMerged: merged, prsClosed: closed } }];

    运行提示词 · 来源原文。Flatten GitHub Stale PRs 节点(Code / JavaScript)

    const results = [];
    for (const item of $input.all()) {
      const body = item.json;
      const list = Array.isArray(body?.items) ? body.items : [];
      for (const issue of list) {
        if (issue && issue.number != null) results.push({ json: issue });
      }
    }
    return results.length ? results : [{ json: { number: null } }];

    运行提示词 · 来源原文。Normalize GitHub Stale Data 节点(Code / JavaScript)

    const staleReviewHours = $('Set Config Fields').item.json.staleReviewHours || 48;
    const cutoffMs = staleReviewHours * 60 * 60 * 1000;
    const now = Date.now();
    const prs = $input.all().map(i => i.json).filter(pr => pr && pr.number != null);
    const stale = prs
      .filter(pr => (now - new Date(pr.created_at).getTime()) > cutoffMs)
      .map(pr => ({
        number: pr.number,
        title: pr.title,
        author: pr.user?.login,
        url: pr.html_url,
        repo: (pr.repository_url || '').split('/repos/')[1],
        hoursOpen: Math.floor((now - new Date(pr.created_at).getTime()) / 3600000)
      }));
    return [{ json: { prsAwaitingReview: stale } }];
  4. OpenAI 生成摘要并解析

    「Merge Overall Digest」将 Jira payload 与 GitHub payload 合并后,送入「Summarize Digest with OpenAI」节点。该节点使用原生 OpenAI Chat Complete,system 提示词要求输出严格 JSON(无 markdown、无代码围栏),包含 whatMoved / whatShipped / whatIsBlocked / needsReview / sprintHealth 五个字段。user 提示词将合并后的数据序列化为 JSON 字符串传入。模型参数:chatModel 取 Config.openAiModel(默认 gpt-4o-mini),maxTokens=2000,temperature=0.2。下游「Parse LLM Summary」Code 节点解析 choices[0].message.content 为 JSON;若解析失败则返回降级摘要并标注错误。

    运行提示词 · 来源原文。Summarize Digest with OpenAI 节点 system message

    You are an engineering-ops assistant. You will be given JSON describing the last 24 hours of Jira and GitHub activity for a software team. Produce a concise daily digest as STRICT JSON only, no markdown, no code fences, matching exactly this shape:
    {
      "whatMoved": "string, 2-4 sentences",
      "whatShipped": "string, 2-4 sentences",
      "whatIsBlocked": "string, 2-4 sentences, mention specific ticket keys",
      "needsReview": "string, 2-4 sentences, mention specific PR numbers/titles",
      "sprintHealth": "one sentence overall read, e.g. On track / At risk / Needs attention, with a short reason"
    }
    Only use the data given. If a category is empty, say so briefly. Do not invent tickets or PRs.

    运行提示词 · 来源原文。Summarize Digest with OpenAI 节点 user message(表达式,运行时求值)

    ={{ JSON.stringify({ staleTicketThresholdDays: $('Set Config Fields').item.json.staleTicketDays, staleReviewThresholdHours: $('Set Config Fields').item.json.staleReviewHours, recentIssues: $json.recentIssues, staleIssues: $json.staleIssues, prsOpened: $json.prsOpened, prsMerged: $json.prsMerged, prsClosed: $json.prsClosed, prsAwaitingReview: $json.prsAwaitingReview }) }}

    运行提示词 · 来源原文。Parse LLM Summary 节点(Code / JavaScript)

    const response = $json;
    const digest = $('Merge Overall Digest').item.json;
    let parsed;
    try {
      const content = response.choices[0].message.content;
      parsed = JSON.parse(content);
    } catch (e) {
      parsed = {
        whatMoved: 'Summary unavailable — the LLM response could not be parsed.',
        whatShipped: '',
        whatIsBlocked: '',
        needsReview: '',
        sprintHealth: 'Unknown — check the digest workflow run for errors.'
      };
    }
    return [{ json: { summary: parsed, digest } }];
  5. 构建并发送 Slack Block Kit 消息

    「Build Slack Message Blocks」Code 节点读取 Config.userMap,将 Jira assigneeAccountId / assignee 与 GitHub author 映射为 Slack @mention(格式 <@USER_ID>)。随后组装 Block Kit JSON:header(📋 Engineering Daily Digest)、sprintHealth section、divider、whatMoved / whatShipped / whatIsBlocked / needsReview 四个 section、divider、stale tickets 列表、PRs awaiting review 列表,以及可选的 context(cc @mentions)。「Post Digest to Slack」节点以 messageType=block 发送,channelId 取 Config.slackChannel 对应的频道。

    运行提示词 · 来源原文。Build Slack Message Blocks 节点(Code / JavaScript)

    const { summary, digest } = $json;
    const userMap = $('Set Config Fields').item.json.userMap || {};
    
    function mention(idOrLogin) {
      const slackId = userMap[idOrLogin];
      return slackId ? `<@${slackId}>` : (idOrLogin || 'unassigned');
    }
    
    const staleMentions = (digest.staleIssues || []).map(i => mention(i.assigneeAccountId || i.assignee));
    const reviewMentions = (digest.prsAwaitingReview || []).map(p => mention(p.author));
    const allMentions = [...new Set([...staleMentions, ...reviewMentions])].filter(m => m.startsWith('<@'));
    
    const blocks = [
      { type: 'header', text: { type: 'plain_text', text: '📋 Engineering Daily Digest' } },
      { type: 'section', text: { type: 'mrkdwn', text: `*Sprint health:* ${summary.sprintHealth}` } },
      { type: 'divider' },
      { type: 'section', text: { type: 'mrkdwn', text: `*What moved*\n${summary.whatMoved}` } },
      { type: 'section', text: { type: 'mrkdwn', text: `*What shipped*\n${summary.whatShipped}` } },
      { type: 'section', text: { type: 'mrkdwn', text: `*What's blocked*\n${summary.whatIsBlocked}` } },
      { type: 'section', text: { type: 'mrkdwn', text: `*Needs review*\n${summary.needsReview}` } },
      { type: 'divider' },
      { type: 'section', text: { type: 'mrkdwn', text: `*Stale tickets (${(digest.staleIssues||[]).length})*\n` + ((digest.staleIssues||[]).map(i => `• <${i.url}|${i.key}> — ${i.summary} (${i.daysStale}d, ${mention(i.assigneeAccountId || i.assignee)})`).join('\n') || '_none_') } },
      { type: 'section', text: { type: 'mrkdwn', text: `*PRs awaiting review (${(digest.prsAwaitingReview||[]).length})*\n` + ((digest.prsAwaitingReview||[]).map(p => `• <${p.url}|#${p.number} ${p.title}> — ${p.hoursOpen}h, ${mention(p.author)}`).join('\n') || '_none_') } }
    ];
    
    if (allMentions.length) {
      blocks.push({ type: 'context', elements: [{ type: 'mrkdwn', text: `cc ${allMentions.join(' ')}` }] });
    }
    
    return [{ json: { blocks, text: `Engineering Daily Digest — ${summary.sprintHealth}`, digest, summary } }];
  6. 可选记录到 Google Sheets

    「If Logging to Sheet Enabled」节点检查 Config.sheetEnabled 是否为 true。若为 true,「Append Row to Google Sheets」节点以 autoMapInputData 模式追加一行,字段包括 ok、channel、message、message_timestamp;需预先确保目标表格包含匹配列名。若为 false,走「Skip Sheet Logging」noOp 分支直接结束。

  7. 权限、邀请与可选用户映射

    1) 在 n8n Credentials 中添加 Jira Software Cloud、GitHub、OpenAI、Slack 凭证(若 sheetEnabled=true 则再加 Google Sheets)。2) GitHub token 需具备 repo 与 pull-request read 权限,以支持 review:none 搜索。3) 在 Slack 中将应用/机器人邀请至目标频道,并确认有 post 权限。4)(可选)在 Config.userMap 中填入 Jira accountId 与 GitHub login 到 Slack member ID 的映射,以启用摘要中的 @mentions。

可复制提示词

搭建指导 · 本站建议版本。搭建助手启动词,用于导入模板前的环境检查与配置确认

这一段是提示词,页面不会在这里插入广告。

你正在搭建一个 n8n 工程日报工作流。请按以下顺序确认:
1) 检查 n8n 实例版本是否支持所用节点类型(scheduleTrigger v1.2, set v3.4, jira v1, github v1.1, openAi v1.1, slack v2.3, googleSheets v4.5, httpRequest v4.2, merge v3, if v2, code v2)。
2) 确认已添加全部所需凭证:Jira Cloud、GitHub、OpenAI、Slack,以及可选的 Google Sheets。
3) 导入 workflow.json 后,先修改 Set Config Fields 中的 jiraBaseUrl、jiraProjectKey、repos、slackChannel 等值为实际项目参数;特别注意 repos 是 JSON 数组格式。
4) 检查 Schedule Trigger 的时区是否与团队所在地一致。
5) 首次运行前建议手动执行一次,观察 OpenAI 返回是否可正常解析为 JSON;若出现解析失败,检查 Parse LLM Summary 的降级输出并调整 system prompt 或 maxTokens。
6) 若启用 sheet logging,先在 Google Sheets 创建对应列(ok, channel, message, message_timestamp),再运行。

先改 Config 再跑;首次建议手动触发验证 OpenAI JSON 解析

来源

Send engineering daily digest to Slack with Jira, GitHub, OpenAI, and Google Sheets · intuz · n8n.io · 2026-09-24

留言与反馈

试过这个方法吗?分享效果、遇到的问题或改进建议。留言审核后公开,请勿填写敏感信息。

正在加载…

    相关用例