8
n8n 中文网amn8n.com

使用 Azure OpenAI (GPT-4o-mini) 和 Gmail 生成 Jira 日终摘要与周报

高级

这是一个自动化工作流,包含 26 个节点。主要使用 Code, Jira, Gmail, GoogleSheets, Agent 等节点。 使用 Azure OpenAI 和 Gmail 生成每日 Jira 摘要和每周报告

前置要求
  • Google 账号和 Gmail API 凭证
  • Google Sheets API 凭证
  • OpenAI API Key

分类

-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "T20oXbS75AjzqlND",
  "meta": {
    "instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177",
    "templateCredsSetupCompleted": true
  },
  "name": "使用 Azure OpenAI (GPT-4o-mini) 和 Gmail 生成 Jira 日终摘要与周报",
  "tags": [],
  "nodes": [
    {
      "id": "cf39d6c2-8075-48bd-90e2-f5aabe04235e",
      "name": "Azure OpenAI 聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
      "position": [
        672,
        640
      ],
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {}
      },
      "credentials": {
        "azureOpenAiApi": {
          "id": "C3WzT18XqF8OdVM6",
          "name": "Azure Open AI account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "bb0d663a-30fd-40dc-affd-b7bedb5d08bf",
      "name": "Azure OpenAI 聊天模型1",
      "type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
      "position": [
        608,
        1600
      ],
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {}
      },
      "credentials": {
        "azureOpenAiApi": {
          "id": "C3WzT18XqF8OdVM6",
          "name": "Azure Open AI account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "654069da-1c35-4475-8a67-1771a9301892",
      "name": "结构化输出解析器",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        736,
        1600
      ],
      "parameters": {
        "jsonSchemaExample": "{\n    \"output\": \"1) **Weekly Overview**  \\nThis week (Oct 13–17, 2025) saw a **total of 5 items completed**, though a significant **backlog of 32 items remains**, indicating an area for improvement in throughput.\\n\\n2) **Weekly Key Metrics**  \\n- **Total items processed (Mon–Fri):** 50  \\n- **Completed (statusCategory: Done or resolution present):** 5  \\n- **In Progress (status.name: In Progress):** 5  \\n- **Backlog/To Do (statusCategory.key: new or status.name: Backlog/To Do):** 32  \\n- **Bugs vs Tasks (issuetype.name counts):** Bugs: 15, Tasks: 15  \\n- **Top assignees (by count, up to 3):** Jyothi Swarup: 50  \\n- **Priority distribution (by priority.name):** Medium: 50  \\n\\n3) **Day-by-Day Highlights**  \\n- **Monday:**  \\n  - Completed 1 task (SCRUM-1: Fix login API timeout)  \\n  - 5 items in progress (SCRUM-5, SCRUM-2, SCRUM-4, SCRUM-6)  \\n- **Tuesday:**  \\n  - No changes in item status, remained in backlog  \\n- **Wednesday:**  \\n  - No changes, all items unchanged  \\n- **Thursday:**  \\n  - SCRUM-3 and SCRUM-4 remain in the backlog, no new progress  \\n- **Friday:**  \\n  - Status unchanged for remaining items  \\n  - Continued backlog accumulation  \\n\\n4) **Progress & Trends**  \\n- Backlog continues to grow with no items moved to completion since Monday.  \\n- Completion rate stabilizing at around **1 item/day only.**  \\n- Backlog items remain unchanged for most of the week.  \\n- All assignee workload appears **highly concentrated**, indicating possible risk.  \\n\\n5) **Blockers & Risks (Week)**  \\n- **Significant backlog:** Many items (SCRUM-2, SCRUM-3, SCRUM-4, SCRUM-6) remain stagnant.  \\n- No visible resolution timelines for in-progress items (particularly SCRUM-5) may lead to increasing risk of missed deadlines.  \\n\\n6) **Recommendations (Next Week)**  \\n- Prioritize backlog resolution by assigning estimates and deadlines for stalled items.  \\n- Focus and urgency required for SCRUM-5 to ensure progress with a clear resolution timeline.  \\n- Consider redistributing tasks or adding resources to handle workload effectively.  \\n- Schedule daily stand-ups to track progress on backlog items and identify blockers.  \\n- Reassess team capacity to meet outstanding sprint deadlines and customer expectations.  \\n\\n7) **Completed Work (Week)**  \\n- SCRUM-1: Fix login API timeout; resolved on 2025-10-13  \\n\\n8) **Data Snapshot (Appendix)**  \\n- { \\\"issuekey\\\": \\\"SCRUM-1\\\", \\\"summary\\\": \\\"Fix login API timeout\\\", \\\"status.name\\\": \\\"Done\\\", \\\"statusCategory.name\\\": \\\"Done\\\", \\\"resolution.name\\\": \\\"Done\\\", \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:19.220+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:36.945+0530\\\" }  \\n- { \\\"issuekey\\\": \\\"SCRUM-2\\\", \\\"summary\\\": \\\"Resolve crash on profile photo upload\\\", \\\"status.name\\\": \\\"Backlog\\\", \\\"statusCategory.name\\\": \\\"To Do\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:20.563+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:41.031+0530\\\" }  \\n- { \\\"issuekey\\\": \\\"SCRUM-5\\\", \\\"summary\\\": \\\"Address janky scroll in feed list\\\", \\\"status.name\\\": \\\"In Progress\\\", \\\"statusCategory.name\\\": \\\"In Progress\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:29.354+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:49.842+0530\\\" }  \"\n  }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "88de2233-3d12-4183-a700-4c40f9c91935",
      "name": "结构化输出解析器1",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        800,
        640
      ],
      "parameters": {
        "jsonSchemaExample": "{\n    \"output\": \"1) EOD Summary  \\n**Today, one item was successfully completed**, reflecting an overall positive outcome and productive effort by the team. Progress was made on several tasks, while a few remained in the backlog.\\n\\n2) Key Metrics (Today)  \\n- Total items processed: 10  \\n- Completed (statusCategory: Done or resolution present): 1  \\n- In Progress (status.name: In Progress): 1  \\n- Backlog/To Do (statusCategory: To Do or status.name: Backlog/To Do): 8  \\n- Bugs vs Tasks (issuetype.name counts): Bugs: 5, Tasks: 5  \\n- Top assignees (by count, up to 3): Jyothi Swarup: 10  \\n- Priority distribution (by priority.name): Medium: 10  \\n\\n3) Notable Updates Today  \\n- SCRUM-1: Done; summary: “Fix login API timeout”; resolutiondate: 2025-10-13T16:10:59.421+0530 IST  \\n- SCRUM-5: In Progress; summary: “Address janky scroll in feed list”; updated: 2025-10-13T16:14:49.842+0530 IST  \\n- SCRUM-2: Backlog; summary: “Resolve crash on profile photo upload”; updated: 2025-10-13T16:14:41.031+0530 IST  \\n- SCRUM-4: Backlog; summary: “Fix dark mode color contrast in buttons”; updated: 2025-10-13T16:14:45.183+0530 IST  \\n- SCRUM-6: Backlog; summary: “Resolve push notification tap not navigating”; updated: 2025-10-13T16:14:51.576+0530 IST  \\n\\n4) Blockers & Risks  \\n- SCRUM-2, SCRUM-3, SCRUM-4, and SCRUM-6 are all currently blocked as they remain in the Backlog.  \\n- **SCRUM-5** is the only task designated as In Progress, though no resolution timeline is indicated. All other items need to be moved forward to avoid further backlog accumulation.\\n\\n5) Actions for Tomorrow  \\n- Assign estimates to Scrum tasks currently in the backlog.  \\n- Increase focus on SCRUM-5 to progress towards completion.  \\n- Identify and address blockers causing delay in Backlog items.  \\n- Re-prioritize items based on urgency and team capacity.  \\n- Conduct a quick review of upcoming sprint timelines to align tasks accordingly.\\n\\n6) Data Snapshot (Appendix)  \\n- { \\\"issuekey\\\": \\\"SCRUM-1\\\", \\\"summary\\\": \\\"Fix login API timeout\\\", \\\"status.name\\\": \\\"Done\\\", \\\"statusCategory.name\\\": \\\"Done\\\", \\\"resolution.name\\\": \\\"Done\\\", \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:19.220+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:36.945+0530\\\" }  \\n- { \\\"issuekey\\\": \\\"SCRUM-2\\\", \\\"summary\\\": \\\"Resolve crash on profile photo upload\\\", \\\"status.name\\\": \\\"Backlog\\\", \\\"statusCategory.name\\\": \\\"To Do\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:20.563+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:41.031+0530\\\" }  \\n- { \\\"issuekey\\\": \\\"SCRUM-5\\\", \\\"summary\\\": \\\"Address janky scroll in feed list\\\", \\\"status.name\\\": \\\"In Progress\\\", \\\"statusCategory.name\\\": \\\"In Progress\\\", \\\"resolution.name\\\": null, \\\"assignee.displayName\\\": \\\"Jyothi Swarup\\\", \\\"priority.name\\\": \\\"Medium\\\", \\\"created\\\": \\\"2025-10-13T13:08:29.354+0530\\\", \\\"updated\\\": \\\"2025-10-13T16:14:49.842+0530\\\" }\"\n  }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "4014c2f1-5ff9-4617-b780-f345669b3a1a",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        496
      ],
      "parameters": {
        "content": "## 每日晚间触发器"
      },
      "typeVersion": 1
    },
    {
      "id": "3d75eeb6-2cac-44a0-8b7a-2462f6fdfe61",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        304
      ],
      "parameters": {
        "height": 176,
        "content": "## 获取所有问题 (Jira)"
      },
      "typeVersion": 1
    },
    {
      "id": "2644c493-fb58-474a-bc2e-89fcf93f8299",
      "name": "获取所有问题",
      "type": "n8n-nodes-base.jira",
      "position": [
        208,
        528
      ],
      "parameters": {
        "options": {},
        "operation": "getAll",
        "returnAll": true
      },
      "credentials": {
        "jiraSoftwareCloudApi": {
          "id": "AnaUUmFzt0vUQADt",
          "name": "jyothi"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f4cee73e-d692-4835-b5a7-c0c94808cb13",
      "name": "扁平化输入",
      "type": "n8n-nodes-base.code",
      "position": [
        432,
        528
      ],
      "parameters": {
        "jsCode": "// n8n Code node: merge all incoming items into a single output array in one item\n\nconst inputItems = $input.all();\nconst combined = [];\n\n// Normalize any value to an array of plain objects\nfunction normalizeToArray(val) {\n  if (typeof val === 'string') {\n    const trimmed = val.trim();\n    try {\n      const parsed = JSON.parse(trimmed);\n      return Array.isArray(parsed) ? parsed : [parsed];\n    } catch {\n      return [{ raw: trimmed }];\n    }\n  }\n  if (Array.isArray(val)) return val;\n  if (val && typeof val === 'object') return [val];\n  return [{ value: val }];\n}\n\n// Merge all items' json into one array\nfor (const item of inputItems) {\n  const source = item.json !== undefined ? item.json : item;\n  const arr = normalizeToArray(source);\n  for (const el of arr) {\n    const obj = el && typeof el === 'object' ? el : { value: el };\n    combined.push(obj);\n  }\n}\n\n// Return a single n8n item containing the whole array\nreturn [\n  {\n    json: {\n      combined,            // All input collapsed into one array\n      count: combined.length\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "7692b76d-68c2-46f0-8d6c-eff609de74b7",
      "name": "创建摘要",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        656,
        416
      ],
      "parameters": {
        "text": "={{ $json.combined }}",
        "options": {
          "systemMessage": "You are an End‑of‑Day (EOD) Report generator for engineering teams. Your job is to read a pasted text blob representing the day’s work (e.g., combined Jira issue array, logs, API responses) and produce a consistent EOD summary every single time using the exact template and rules below. Do not ask questions. Infer context only from the input.\n\nINPUT FORMAT\n- The input may be a single object containing { combined: [...], count: N } or a raw array of issue/work items.\n- Treat JSON keys/values and log lines as source of truth.\n- Today’s local time context: Asia/Kolkata (IST), unless clearly indicated otherwise.\n\nOUTPUT TEMPLATE\n1) EOD Summary\n- One concise paragraph describing today’s overall outcome and the most important takeaway (e.g., completion, progress, blockers).\n\n2) Key Metrics (Today)\n- Total items processed:\n- Completed (statusCategory: Done or resolution present):\n- In Progress (status.name: In Progress):\n- Backlog/To Do (statusCategory: To Do or status.name: Backlog/To Do):\n- Bugs vs Tasks (issuetype.name counts):\n- Top assignees (by count, up to 3):\n- Priority distribution (by priority.name):\n\n3) Notable Updates Today\n- 3–6 bullets referencing specific keys and summaries (e.g., SCRUM-1: Done; summary: “Fix login API timeout”; resolutiondate/timestamp if present, in IST label).\n\n4) Blockers & Risks\n- Bullets inferred from statuses, null fields (e.g., timeoriginalestimate), overdue sprint windows, inconsistent “active” vs past endDate, or items showing no progress (created vs updated).\n\n5) Actions for Tomorrow\n- 4–7 prioritized, action‑oriented steps tied to blockers and carryover (assign, add estimate, re‑prioritize, resolve conflicts, test/monitor, etc.).\n\n6) Data Snapshot (Appendix)\n- Quote minimal exact fields for 3–5 representative records (issue key, summary, status.name, statusCategory.name, resolution.name, assignee.displayName, priority.name, created, updated). Use original field names exactly.\n\nCLASSIFICATION RULES\n- Completed = statusCategory.key: done OR resolution present.\n- In Progress = status.name: In Progress OR statusCategory.key: indeterminate.\n- Backlog/To Do = status.name: Backlog/To Do OR statusCategory.key: new.\n- Bugs vs Tasks from issuetype.name.\n- Use input’s combined array if present; otherwise use the root array.\n- When timestamps are reported in readable form, label clearly as IST; otherwise preserve originals.\n\nFORMATTING RULES\n- Be concise; no fluff.\n- Headings and section order must never change.\n- Preserve field names exactly (e.g., statuscategorychangedate).\n- Use bold sparingly for 2–4 critical items/terms.\n- If a section has no data, write “None”.\n- Do not add links unless present in the input.\n\nERROR HANDLING\n- If the input appears incomplete or malformed, write “Input appears incomplete or malformed” in EOD Summary, then proceed with whatever can be extracted.\n\nCONSISTENCY CHECK\n- Ensure all six sections are present, labels match exactly, counts are internally consistent, and referenced keys/summaries match the input."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "514fc953-ea89-46e4-b1c0-c05367d978df",
      "name": "追加摘要",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1008,
        528
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $now.format('dd-MMM-yyyy') }}",
            "JSON": "={{ $('Flatten Input').item.json.combined }}",
            "Summary": "={{ $json.output }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "JSON",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "JSON",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Summary",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit?usp=drivesdk",
          "cachedResultName": "JIRA Daily Reporting"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "ajCmdXdhjJqZW6RE",
          "name": "automations"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "30e6b160-c87c-40ca-b2cd-75f5ae93d9ff",
      "name": "每日晚间触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -16,
        528
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1e5d7fbe-718a-472f-8d44-efc621042073",
      "name": "周五晚间触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -80,
        1376
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                5
              ],
              "triggerAtHour": 20
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f4c8a646-d963-4b84-b442-91ecad3b31d8",
      "name": "获取所有存储数据",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        144,
        1376
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/14iOjPbhS1kw2sFJy-N-hGads94iz38OjKzlMq8dkgc0/edit?usp=drivesdk",
          "cachedResultName": "JIRA Daily Reporting"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "ajCmdXdhjJqZW6RE",
          "name": "automations"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "d26c622b-92f7-4826-adeb-ddf57df9daf2",
      "name": "扁平化输入",
      "type": "n8n-nodes-base.code",
      "position": [
        368,
        1376
      ],
      "parameters": {
        "jsCode": "// n8n Code node: merge all incoming items into a single output array in one item\n\nconst inputItems = $input.all();\nconst combined = [];\n\n// Normalize any value to an array of plain objects\nfunction normalizeToArray(val) {\n  if (typeof val === 'string') {\n    const trimmed = val.trim();\n    try {\n      const parsed = JSON.parse(trimmed);\n      return Array.isArray(parsed) ? parsed : [parsed];\n    } catch {\n      return [{ raw: trimmed }];\n    }\n  }\n  if (Array.isArray(val)) return val;\n  if (val && typeof val === 'object') return [val];\n  return [{ value: val }];\n}\n\n// Merge all items' json into one array\nfor (const item of inputItems) {\n  const source = item.json !== undefined ? item.json : item;\n  const arr = normalizeToArray(source);\n  for (const el of arr) {\n    const obj = el && typeof el === 'object' ? el : { value: el };\n    combined.push(obj);\n  }\n}\n\n// Return a single n8n item containing the whole array\nreturn [\n  {\n    json: {\n      combined,            // All input collapsed into one array\n      count: combined.length\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a626ce54-881d-4dbc-88ae-19adad2f6389",
      "name": "创建周度摘要",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        592,
        1376
      ],
      "parameters": {
        "text": "={{ $json.combined }}",
        "options": {
          "systemMessage": "You are a Weekly Summary generator for engineering teams. Your job is to read five day-level inputs (Monday through Friday)—each containing data (arrays/objects), JSON details, and an End‑of‑Day (EOD) summary—and produce a consistent Weekly Summary every single time using the exact template and rules below. Do not ask questions. Infer context only from the input.\n\nINPUT FORMAT\n- The input will include five day entries: Monday, Tuesday, Wednesday, Thursday, Friday.\n- Each day may have:\n  - Data: arrays/objects of issues/work items.\n  - JSON: raw or stringified JSON representing items.\n  - Summary: an EOD summary string following a known template.\n- Treat JSON keys/values and EOD text as source of truth.\n- If multiple structures are present (e.g., combined arrays, raw arrays, stringified JSON), parse/normalize and merge.\n\nOUTPUT TEMPLATE\n1) Weekly Overview\n- One concise paragraph stating the week window (Mon–Fri), overall outcome, and the single most important takeaway (e.g., completion count, major blocker cleared, quality trend).\n\n2) Weekly Key Metrics\n- Total items processed (Mon–Fri):\n- Completed (statusCategory: Done or resolution present):\n- In Progress (status.name: In Progress):\n- Backlog/To Do (statusCategory.key: new or status.name: Backlog/To Do):\n- Bugs vs Tasks (issuetype.name counts):\n- Top assignees (by count, up to 3):\n- Priority distribution (by priority.name):\n\n3) Day-by-Day Highlights\n- Monday: 2–4 bullets of notable events (keys, summaries, key timestamps), or “None”.\n- Tuesday: 2–4 bullets, or “None”.\n- Wednesday: 2–4 bullets, or “None”.\n- Thursday: 2–4 bullets, or “None”.\n- Friday: 2–4 bullets, or “None”.\n\n4) Progress & Trends\n- 3–6 bullets comparing days (e.g., rising backlog, stabilization in login, throughput changes, increase/decrease in Done, assignee load shifts).\n\n5) Blockers & Risks (Week)\n- Bullets inferred from the aggregate: persistent backlog items, missing estimates, conflicts (e.g., active sprint with past endDate), stalled items (unchanged status across days), or repeated failures.\n\n6) Recommendations (Next Week)\n- 5–8 prioritized, action‑oriented steps tied to blockers, throughput, and quality trends. Keep each specific and measurable.\n\n7) Completed Work (Week)\n- 3–8 bullets referencing key completions with issue keys, summaries, resolution/resolutiondate/timestamps (label IST if converting).\n\n8) Data Snapshot (Appendix)\n- Quote minimal exact fields for 5–10 representative records across the week:\n  - issue key, summary, status.name, statusCategory.name/key, resolution.name, assignee.displayName, priority.name, created, updated.\n  - Preserve original field names exactly as found.\n\nCLASSIFICATION & AGGREGATION RULES\n- Completed = statusCategory.key: done OR resolution present.\n- In Progress = status.name: In Progress OR statusCategory.key: indeterminate.\n- Backlog/To Do = status.name: Backlog/To Do OR statusCategory.key: new.\n- Bugs vs Tasks from issuetype.name.\n- Normalize sources in this order per day: combined array → root array/object → stringified JSON → EOD Summary counts (as fallback only).\n- When computing weekly metrics, sum unique items across all days (deduplicate by key if present).\n- Age/staleness: use created vs updated; note items unchanged across multiple days.\n- Timezone: Prefer Asia/Kolkata (IST) when converting; if preserving originals, label as-is.\n\nFORMATTING RULES\n- Be concise; no fluff.\n- Headings and section order must never change.\n- Preserve field names exactly (e.g., statuscategorychangedate).\n- Use bold sparingly for 3–6 critical items/terms.\n- If a section has no data, write “None”.\n- Do not add links unless present in the input.\n\nERROR HANDLING\n- If one or more days are missing or malformed, state “Some daily inputs appear incomplete or malformed” in Weekly Overview, then proceed with whatever can be extracted.\n- If a metric cannot be computed due to missing fields, set it to “Unknown” and briefly explain under Blockers & Risks.\n\nCONSISTENCY CHECK\n- Ensure all eight sections are present, labels match exactly, counts are internally consistent, and referenced keys/summaries match the input."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "82ae1eaa-b298-4a81-a839-248558ce2fd9",
      "name": "创建邮件",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        1376
      ],
      "parameters": {
        "jsCode": "// filename: n8n-generate-weekly-summary-email.js\n\n/**\n * n8n Code node (JavaScript) – Fixes “Code doesn't return items properly”\n * and returns a valid array of items with the generated HTML email.\n *\n * Input expectation:\n * - Each incoming item has a field `output` (string) containing the weekly summary text\n *   with sections 1) .. 8) as provided by your AI Agent node.\n *\n * Output:\n * - Returns an array with ONE item:\n *     { json: { html: \"<!-- full HTML email -->\", subject: \"Weekly Summary\", plainTextPreview: \"...\" } }\n *\n * If multiple input items exist, it uses the first one with `output` present.\n *\n * Place this code in your “Code” node and ensure the previous node passes an item\n * with `output` in `item.json.output`.\n */\n\n// --------------- Helper functions ---------------\n\n// Basic HTML escaping\nfunction escapeHtml(str) {\n  return String(str)\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;');\n}\n\n// Convert \"1) **Weekly Overview**\" -> \"Weekly Overview\"\nfunction cleanHeading(headingText) {\n  let h = headingText.replace(/^\\d\\)\\s*/, '');\n  h = h.replace(/\\*\\*/g, '').trim();\n  return h;\n}\n\n// Inline formatting: bold **text**, issue keys as pills\nfunction inlineFormat(text) {\n  let t = escapeHtml(text);\n  t = t.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');\n  t = t.replace(/\\b([A-Z]+-\\d+)\\b/g, '<span class=\"pill\">$1</span>');\n  return t;\n}\n\n// Render section content: bullets, paragraphs, snapshot code blocks\nfunction renderSectionContent(content) {\n  if (!content) return '<p class=\"muted\">None</p>';\n\n  const lines = content.split('\\n');\n  const blocks = [];\n  let currentList = null;\n\n  const flushList = () => {\n    if (currentList && currentList.items.length) {\n      const li = currentList.items.map(i => `<li>${inlineFormat(i)}</li>`).join('');\n      blocks.push(`<ul class=\"metric-list\">${li}</ul>`);\n    }\n    currentList = null;\n  };\n\n  for (let rawLine of lines) {\n    const line = rawLine.trim();\n\n    if (line.length === 0) {\n      flushList();\n      continue;\n    }\n    if (/^-\\s+/.test(line)) {\n      if (!currentList) currentList = { items: [] };\n      currentList.items.push(line.replace(/^-\\s+/, '').trim());\n      continue;\n    }\n    flushList();\n\n    // JSON-like snapshot lines -> code block\n    if (/^\\{.+\\}$/.test(line) || line.startsWith('{ \"') || line.startsWith('- {')) {\n      const cleaned = line.replace(/^-+\\s*/, '');\n      blocks.push(`<div class=\"code\">${escapeHtml(cleaned)}</div>`);\n    } else {\n      blocks.push(`<p>${inlineFormat(line)}</p>`);\n    }\n  }\n\n  flushList();\n  return blocks.join('\\n');\n}\n\n// Build email HTML from the `output` string\nfunction buildEmailHtml(outputText) {\n  // Find section headings like \"1) **Weekly Overview**\"\n  const headingRegex = /\\n?\\s*(\\d\\)\\s\\*\\*?[^\\n]+?\\*\\*?\\s*)/g;\n  const parts = [];\n  let match;\n\n  while ((match = headingRegex.exec(outputText)) !== null) {\n    const headingText = match[1];\n    const startIndexForContent = headingRegex.lastIndex;\n    const cleanedHeading = cleanHeading(headingText);\n\n    // Set previous section content boundary\n    if (parts.length > 0) {\n      parts[parts.length - 1].content = outputText.slice(parts[parts.length - 1].startIndex, match.index).trim();\n    }\n\n    parts.push({\n      heading: cleanedHeading,\n      startIndex: startIndexForContent,\n      content: '',\n    });\n  }\n\n  if (parts.length > 0) {\n    const last = parts[parts.length - 1];\n    last.content = outputText.slice(last.startIndex).trim();\n  } else {\n    // Fallback: whole text as one section\n    parts.push({\n      heading: 'Weekly Summary',\n      content: outputText.trim(),\n    });\n  }\n\n  const sectionHtml = parts.map(({ heading, content }) => {\n    return `\n      <section class=\"card\">\n        <h2 class=\"section-title\">${escapeHtml(heading)}</h2>\n        ${renderSectionContent(content)}\n      </section>\n    `;\n  }).join('\\n');\n\n  const generatedAtIst = escapeHtml(new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' }));\n\n  const html = `\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Weekly Summary</title>\n<style>\n  :root {\n    --bg: #f7f8fa;\n    --card-bg: #ffffff;\n    --text: #111827;\n    --muted: #6b7280;\n    --primary: #2563eb;\n    --border: #e5e7eb;\n  }\n  body {\n    margin: 0;\n    background: var(--bg);\n    color: var(--text);\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Inter, Oxygen, Ubuntu, Cantarell, \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", Arial, sans-serif;\n    line-height: 1.6;\n  }\n  .container {\n    max-width: 760px;\n    margin: 0 auto;\n    padding: 24px 16px;\n  }\n  .header {\n    background: linear-gradient(135deg, #eef2ff, #f0fdf4);\n    border: 1px solid var(--border);\n    border-radius: 14px;\n    padding: 20px;\n    margin-bottom: 16px;\n  }\n  .title {\n    margin: 0;\n    font-size: 22px;\n    font-weight: 700;\n  }\n  .subtitle {\n    margin: 6px 0 0;\n    font-size: 14px;\n    color: var(--muted);\n  }\n  .card {\n    background: var(--card-bg);\n    border: 1px solid var(--border);\n    border-radius: 12px;\n    padding: 18px;\n    margin: 12px 0;\n    box-shadow: 0 2px 6px rgba(31, 41, 55, 0.06);\n  }\n  .section-title {\n    margin: 0 0 12px;\n    font-size: 18px;\n    font-weight: 700;\n    color: var(--text);\n    border-left: 3px solid var(--primary);\n    padding-left: 10px;\n  }\n  p { margin: 10px 0; }\n  ul.metric-list { padding-left: 18px; margin: 8px 0; }\n  ul.metric-list li { margin: 6px 0; }\n  .pill {\n    display: inline-block;\n    font-size: 12px;\n    padding: 4px 8px;\n    border-radius: 999px;\n    background: #eef2ff;\n    color: #1e3a8a;\n    border: 1px solid #e5e7eb;\n    margin-right: 6px;\n  }\n  .code {\n    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n    font-size: 12px;\n    background: #f3f4f6;\n    border: 1px solid var(--border);\n    border-radius: 8px;\n    padding: 8px;\n    white-space: pre-wrap;\n    word-break: break-word;\n    margin: 8px 0;\n  }\n  .footer {\n    text-align: center;\n    color: var(--muted);\n    font-size: 12px;\n    margin-top: 18px;\n  }\n  @media (max-width: 480px) {\n    .container { padding: 18px 12px; }\n    .title { font-size: 20px; }\n    .section-title { font-size: 17px; }\n  }\n</style>\n</head>\n<body>\n  <div class=\"container\">\n    <header class=\"header\">\n      <h1 class=\"title\">Weekly Summary</h1>\n      <p class=\"subtitle\">Generated automatically from your weekly report data.</p>\n    </header>\n\n    ${sectionHtml}\n\n    <footer class=\"footer\">\n      Sent via automated workflow · ${generatedAtIst} IST\n    </footer>\n  </div>\n</body>\n</html>\n  `.trim();\n\n  return html;\n}\n\n// Create a short plain text preview (for email providers that show a preview line)\nfunction buildPlainPreview(outputText) {\n  // Take the first line after Weekly Overview for a preview\n  const firstLine = outputText.split('\\n').find(l => l.trim().length > 0) || 'Weekly Summary';\n  return firstLine.replace(/\\*\\*/g, '').replace(/\\s+/g, ' ').trim().slice(0, 160);\n}\n\n// --------------- Main n8n code ---------------\n\nconst items = $input.all();\n\n// Pick the first item that has `output`\nlet outputText = null;\nfor (const it of items) {\n  if (it && it.json && typeof it.json.output === 'string' && it.json.output.trim().length > 0) {\n    outputText = it.json.output;\n    break;\n  }\n}\n\n// If not found, try the first item's whole JSON converted to string\nif (!outputText) {\n  if (items.length > 0 && items[0].json) {\n    outputText = JSON.stringify(items[0].json);\n  } else {\n    outputText = 'Weekly Summary\\n\\nNo output text found in incoming items.';\n  }\n}\n\n// Build HTML and preview\nconst html = buildEmailHtml(outputText);\nconst preview = buildPlainPreview(outputText);\n\n// Return one n8n item with the email payload\nreturn [\n  {\n    json: {\n      subject: 'Weekly Summary',\n      html,\n      plainTextPreview: preview,\n    },\n  },\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9d53e156-2c55-412f-a342-ac7b15be99f9",
      "name": "发送邮件给相关方",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1168,
        1376
      ],
      "webhookId": "32493ed3-072e-43f9-a3ef-7623fdf6e4cf",
      "parameters": {
        "sendTo": "jyothi.swarup@techdome.net.in",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "={{ $json.subject }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "70f5n8rPahCANHs7",
          "name": "jyothi"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "551aec75-3b84-4fe9-a859-c224e194463f",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        688
      ],
      "parameters": {
        "height": 176,
        "content": "## 扁平化输入"
      },
      "typeVersion": 1
    },
    {
      "id": "74294bfe-8fcb-4b01-b3de-015ba940405e",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        176
      ],
      "parameters": {
        "height": 192,
        "content": "## 聊天模型 (日终)"
      },
      "typeVersion": 1
    },
    {
      "id": "c02f0d08-1761-4f59-bdde-1c430082776f",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        704
      ],
      "parameters": {
        "height": 192,
        "content": "## 追加摘要 (表格)"
      },
      "typeVersion": 1
    },
    {
      "id": "8539c40a-70dc-4e66-a2a4-02fe9deca977",
      "name": "便签5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        1360
      ],
      "parameters": {
        "height": 176,
        "content": "## 周五晚间触发器"
      },
      "typeVersion": 1
    },
    {
      "id": "affe1837-b2b5-4337-80a0-59adca265027",
      "name": "便签6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        1168
      ],
      "parameters": {
        "height": 176,
        "content": "## 获取所有存储数据 (表格)"
      },
      "typeVersion": 1
    },
    {
      "id": "2e86ba0f-1810-478b-b68b-f5e949b442e8",
      "name": "便签7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        304,
        1536
      ],
      "parameters": {
        "height": 176,
        "content": "## 扁平化输入 (周度)"
      },
      "typeVersion": 1
    },
    {
      "id": "5b3bc309-60b3-467c-bc62-3a04b7b52e32",
      "name": "便签8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        576,
        1168
      ],
      "parameters": {
        "height": 176,
        "content": "## 聊天模型 (周度)"
      },
      "typeVersion": 1
    },
    {
      "id": "6ab5b8a2-7aa4-4795-aa2b-2514b5045776",
      "name": "便签9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        1536
      ],
      "parameters": {
        "height": 176,
        "content": "## 创建邮件"
      },
      "typeVersion": 1
    },
    {
      "id": "91a7270d-1ad6-4d10-9182-29068b710763",
      "name": "便签10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1104,
        1168
      ],
      "parameters": {
        "height": 176,
        "content": "## 发送邮件给相关方"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "8897253f-0b1a-4ea3-bf93-9b00f633c6e3",
  "connections": {
    "Create Email": {
      "main": [
        [
          {
            "node": "Email to Stakeholders",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten Input": {
      "main": [
        [
          {
            "node": "Create Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Summary": {
      "main": [
        [
          {
            "node": "Append Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get ALL issues": {
      "main": [
        [
          {
            "node": "Flatten Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten the input": {
      "main": [
        [
          {
            "node": "Create Weekly Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get all stored data": {
      "main": [
        [
          {
            "node": "Flatten the input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Weekly Summary": {
      "main": [
        [
          {
            "node": "Create Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Evening Trigger": {
      "main": [
        [
          {
            "node": "Get ALL issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Friday Evening Trigger": {
      "main": [
        [
          {
            "node": "Get all stored data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Azure OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Create Summary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Azure OpenAI Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Create Weekly Summary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Create Weekly Summary",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser1": {
      "ai_outputParser": [
        [
          {
            "node": "Create Summary",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量26
分类-
节点类型9
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Rahul Joshi

Rahul Joshi

@rahul08

Rahul Joshi is a seasoned technology leader specializing in the n8n automation tool and AI-driven workflow automation. With deep expertise in building open-source workflow automation and self-hosted automation platforms, he helps organizations eliminate manual processes through intelligent n8n ai agent automation solutions.

外部链接
在 n8n.io 查看

分享此工作流