8
n8n 中文网amn8n.com

从 Notion 内容创建 Linear 工单

高级

这是一个Engineering, Product领域的自动化工作流,包含 24 个节点。主要使用 If, Set, Code, Filter, Linear 等节点。 从 Notion 内容创建 Linear 工单

前置要求
  • Notion API Key
  • 可能需要目标 API 的认证凭证
  • OpenAI API Key
  • HTTP Webhook 端点(n8n 会自动生成)
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "meta": {
    "instanceId": "cb484ba7b742928a2048bf8829668bed5b5ad9787579adea888f05980292a4a7"
  },
  "nodes": [
    {
      "id": "7d11aa76-c7bf-4aa3-9f94-fb2231f5055b",
      "name": "遍历项目",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1460,
        2080
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "bea6febe-077f-4b90-887c-9c82954ef5d9",
      "name": "获取 Linear 团队详情",
      "type": "n8n-nodes-base.graphql",
      "position": [
        -1220,
        1760
      ],
      "parameters": {
        "query": "=query GetTeamsAndProjects {\n  teams(filter: {name: {contains: \"{{ $json['Linear team name'] }}\"}}) {\n    nodes {\n      id\n      name\n      members {\n        nodes {\n          id\n          name\n          email\n        }\n      }\n      projects {\n        nodes {\n          id\n          name\n          description\n        }\n      }\n    }\n  }\n}\n",
        "endpoint": "https://api.linear.app/graphql",
        "requestMethod": "GET",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "zYILrk4RKFqdP66s",
          "name": "[Omar] Notion credentials for GraphQL API"
        }
      },
      "executeOnce": true,
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "27a2111b-716b-4150-af92-ab90d7e83642",
      "name": "获取问题内容",
      "type": "n8n-nodes-base.notion",
      "position": [
        -1220,
        2340
      ],
      "parameters": {
        "blockId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set assignee and title').item.json.id }}"
        },
        "resource": "block",
        "operation": "getAll",
        "returnAll": true,
        "simplifyOutput": false,
        "fetchNestedBlocks": true
      },
      "credentials": {
        "notionApi": {
          "id": "80",
          "name": "Notion david-internal"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "96bde9b1-b84e-445a-b90c-6dd4031076aa",
      "name": "聚合",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -560,
        2340
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "markdown"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "45c63a2d-d457-4882-ac99-4b8e4a63ae43",
      "name": "准备问题数据",
      "type": "n8n-nodes-base.set",
      "position": [
        -1220,
        2620
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e1b44489-ee32-4da4-816e-f56d640a9731",
              "name": "title",
              "type": "string",
              "value": "={{ $if($('Set assignee and title').item.json.title.length <= 70, $('Set assignee and title').item.json.title, $('Shorten title').item.json.message.content) }}"
            },
            {
              "id": "f3fab4f6-8ea3-4b93-91ea-ec08c2d9eded",
              "name": "description",
              "type": "string",
              "value": "=_Issue created automatically from a [Notion block]({{ $('Set page URL').last().json.page_url + '?pvs=4#' + $('Loop Over Items').item.json.id.replaceAll('-', '') }})_ {{ $if($('Set assignee and title').item.json.assignee_fragment && !$('Set assignee and title').item.json.assignee, \"\\nAssignee '\" + $('Set assignee and title').item.json.assignee_fragment + \"' not found\", '') }}\n\n{{ $json.markdown?.join('\\n') }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "ce619c7d-8363-48ec-86c8-a1f16097eb3e",
      "name": "创建 Linear 问题",
      "type": "n8n-nodes-base.linear",
      "position": [
        -1000,
        2620
      ],
      "parameters": {
        "title": "={{ $json.title }}",
        "teamId": "={{ $('Set team ID').item.json.team_id }}",
        "additionalFields": {
          "assigneeId": "={{ $('Set assignee and title').item.json.assignee.id }}",
          "description": "={{ $json.description }}"
        }
      },
      "credentials": {
        "linearApi": {
          "id": "218",
          "name": "Linear account (David)"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "cc365fa1-a2cc-430c-8cb4-5d708b7a66b9",
      "name": "设置分配人和标题",
      "type": "n8n-nodes-base.code",
      "position": [
        -1220,
        2080
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Set the title and the assignee based on the first line of the item\n\nlet firstLine = $json[$json.type].text.reduce((s, o) => {\n  return s + o.text.content\n}, \"\")\nconsole.log('firstLin', firstLine)\nconst regex = /^(\\[[^\\]]*\\]\\s)?(.+)$/;\nconst match = firstLine.match(regex);\nconsole.log('match', match)\n\nif (match) {\n  // If the first part is not present, match[1] will be undefined\n  item.json.assignee_fragment = match[1]?.slice(1, -2) || null;\n  item.json.title = match[2];\n} else {\n  item.json.title = firstLine;\n  item.json.assignee_fragment = null;\n}\n\n// Set the new title in Notion format\n// $url will be set later, once we have it\nconst prefix_link = [\n  {\"text\":{\"content\":\"[\"}},\n  {\"text\":{\"content\":\"In Linear\", \"link\":{\"url\": \"$url\"} }},\n  {\"text\":{\"content\":\"] \"}}\n]\nitem.json.new_content = {\n  \"rich_text\": [...prefix_link, ...item.json.to_do.text]\n}\n\n// Find a matching assignee\nconst members = $('Fetch Linear team details').item.json.data.teams.nodes[0].members.nodes\nconsole.log('people', members)\nconsole.log('fragment', item.json.assignee_fragment)\nconst matching_people = members.filter(p => \n  p.name.toLowerCase().startsWith(item.json.assignee_fragment?.toLowerCase())\n)\nconsole.log('mpeople', matching_people)\nif (matching_people.length > 0) {\n  item.json.assignee = matching_people[0]\n}\n\nitem.pairedItem = 0\n\nreturn item"
      },
      "typeVersion": 2
    },
    {
      "id": "15d9c526-95f3-4209-9a9f-a0ba4c09a67e",
      "name": "团队缺失?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1000,
        1760
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "047fbe62-ebab-44ab-89b1-232f5f15874d",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.data.teams?.nodes?.length < 1 }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "491a1176-18a7-442a-8f64-a8c946ac25dc",
      "name": "设置页面 URL",
      "type": "n8n-nodes-base.set",
      "position": [
        -1000,
        2340
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0b0ced59-14c9-43e9-a5ee-f4b1862fccd6",
              "name": "page_url",
              "type": "string",
              "value": "={{ $('n8n Form Trigger').item.json['Notion block URL'].substr(0, $('n8n Form Trigger').item.json['Notion block URL'].indexOf('?')) || $('n8n Form Trigger').item.json['Notion block URL'] }}"
            },
            {
              "id": "3df2b2e6-38ca-4fb6-b00c-e3a6ceb3f9b3",
              "name": "root_content",
              "type": "object",
              "value": "={{ $('Set assignee and title').item.json[$('Set assignee and title').item.json.type] }}"
            },
            {
              "id": "41a18b43-49fd-45a5-850d-55b9f08f9b93",
              "name": "root_id",
              "type": "string",
              "value": "={{ $('Set assignee and title').item.json.id }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.3
    },
    {
      "id": "bd107990-b009-4b81-8fee-a80c9ab09b4c",
      "name": "设置团队 ID",
      "type": "n8n-nodes-base.set",
      "position": [
        -740,
        1760
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "b22a4a67-67b5-415a-ab38-4d7f781e8b7e",
              "name": "team_id",
              "type": "string",
              "value": "={{ $json.data.teams.nodes[0].id }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "010105b6-38a2-4859-9e5d-b9624addeacc",
      "name": "向 Notion 块添加链接",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -560,
        2620
      ],
      "parameters": {
        "url": "=https://api.notion.com/v1/blocks/{{ $('Loop Over Items').item.json.id }}",
        "method": "PATCH",
        "options": {},
        "jsonBody": "={\n  \"to_do\":\n    {{ JSON.stringify($('Set assignee and title').item.json.new_content).replace('$url', $json.data.issue.url) }}\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        },
        "nodeCredentialType": "notionApi"
      },
      "credentials": {
        "notionApi": {
          "id": "80",
          "name": "Notion david-internal"
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "bb88f7df-deef-4c9d-b8af-3e59bf0e0b7d",
      "name": "获取问题 URL",
      "type": "n8n-nodes-base.graphql",
      "position": [
        -780,
        2620
      ],
      "parameters": {
        "query": "=query IssueDetails {\n  issue(id: \"{{ $json.id }}\") {\n    url\n  }\n}",
        "endpoint": "https://api.linear.app/graphql",
        "requestMethod": "GET",
        "authentication": "headerAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "zYILrk4RKFqdP66s",
          "name": "[Omar] Notion credentials for GraphQL API"
        }
      },
      "executeOnce": true,
      "typeVersion": 1,
      "continueOnFail": true
    },
    {
      "id": "00736596-08ee-4ff6-b907-bd454dd406d9",
      "name": "缩短标题",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -1000,
        2080
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4",
          "cachedResultName": "GPT-4"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Make the following text more concise, so that it's max 150 chars long. If it's already less than 70 chars long, just return the original text. Do not return anything else other than the text.\n\nTEXT:\n{{ $json.title }}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "id": "VQtv7frm7eLiEDnd",
          "name": "OpenAi account 7"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "729928a2-8a5e-4b25-82be-ba1916b9953f",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1260,
        2020
      ],
      "parameters": {
        "color": 7,
        "width": 877.8549621677266,
        "height": 214.7985362687051,
        "content": "### 确定问题分配人和标题(必要时缩短)"
      },
      "typeVersion": 1
    },
    {
      "id": "86d3b72f-f235-4b37-877f-c8ab1f39ba30",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1260,
        2280
      ],
      "parameters": {
        "color": 7,
        "width": 877.8549621677266,
        "height": 216.9904777194533,
        "content": "### 编写问题描述"
      },
      "typeVersion": 1
    },
    {
      "id": "01c06352-76fa-4d63-b37b-2a0239d81302",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1260,
        2560
      ],
      "parameters": {
        "color": 7,
        "width": 877.8549621677266,
        "height": 216.9904777194533,
        "content": "### 创建问题并在 Notion 中添加链接"
      },
      "typeVersion": 1
    },
    {
      "id": "1f152f08-0c5a-4806-bd85-7e9a5e386fe4",
      "name": "便签5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1260,
        1500
      ],
      "parameters": {
        "color": 7,
        "width": 1164.99929221574,
        "height": 442.760447146518,
        "content": "### 从 Notion 获取要创建的问题(并加载 Linear 团队详情)"
      },
      "typeVersion": 1
    },
    {
      "id": "839d1815-419d-4acb-8668-94f1cbe45f1f",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1780,
        1700
      ],
      "parameters": {
        "height": 278.9250421966361,
        "content": "# 试用我"
      },
      "typeVersion": 1
    },
    {
      "id": "f9bc6e67-a9f0-4c9b-a9b3-fdea1fd9de3e",
      "name": "仅未导入、未选中的待办事项块",
      "type": "n8n-nodes-base.filter",
      "position": [
        -220,
        1760
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d7e85c09-8548-4fc8-a8a9-636e4529e9d9",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.type }}",
              "rightValue": "to_do"
            },
            {
              "id": "13fb565d-8951-4c89-9684-85c357459794",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ !$json.to_do.text.reduce((s, o) => s + o.plain_text, \"\").startsWith('[In Linear]') }}",
              "rightValue": ""
            },
            {
              "id": "0a9c8e94-11ec-4317-8de5-f22862555b78",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.to_do.checked }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "186a4272-4550-441e-9ef2-66de2dac5b8a",
      "name": "n8n Form Trigger",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -1460,
        1760
      ],
      "webhookId": "5a631d63-f899-4967-acad-69924674e96a",
      "parameters": {
        "path": "5a631d63-f899-4967-acad-69924674e96a",
        "formTitle": "Import Linear issues from Notion",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Notion page URL",
              "requiredField": true
            },
            {
              "fieldType": "dropdown",
              "fieldLabel": "Linear team name",
              "fieldOptions": {
                "values": [
                  {
                    "option": "AI"
                  },
                  {
                    "option": "Adore"
                  },
                  {
                    "option": "Payday"
                  },
                  {
                    "option": "NODES"
                  }
                ]
              },
              "requiredField": true
            }
          ]
        },
        "responseMode": "responseNode",
        "formDescription": "More information on Notion formatting required here: https://www.notion.so/n8n/8848dd09892341969faedd1313eea586"
      },
      "typeVersion": 2
    },
    {
      "id": "0fed5dbe-54e9-4bc3-8ab3-6175347ecce7",
      "name": "获取问题",
      "type": "n8n-nodes-base.notion",
      "onError": "continueErrorOutput",
      "position": [
        -500,
        1760
      ],
      "parameters": {
        "blockId": {
          "__rl": true,
          "mode": "url",
          "value": "={{ $('n8n Form Trigger').item.json['Notion page URL'] }}"
        },
        "resource": "block",
        "operation": "getAll",
        "returnAll": true,
        "simplifyOutput": false
      },
      "credentials": {
        "notionApi": {
          "id": "80",
          "name": "Notion david-internal"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "7cc756ad-55f3-4596-989a-df90d4c829c7",
      "name": "将内容转换为 Markdown",
      "type": "n8n-nodes-base.code",
      "position": [
        -780,
        2340
      ],
      "parameters": {
        "jsCode": "function extractMarkdown(obj) {\n  console.log('obj', obj.text)\n  return obj.text.reduce((s, o) => {\n    if(o.text?.link) {\n      return s + '[' + o.text.content + '](' + o.text.link?.url + ')'\n    }\n    return s + o.text.content\n  }, \"\")\n}\n\n\nconst indent = \"    \"; // Four spaces\nlet parent_ids = [$input.all()[0].json.root_id]\n\nfor(item of $input.all()){\n\n  // Generate the markdown\n\n  if(item.json.type) {\n  \n    const type = item.json.type\n    \n    if(type == 'bulleted_list_item' || type == 'toggle') {\n      item.json.markdown = '* ' + extractMarkdown(item.json[type])\n    } else if(type == 'numbered_list_item') {\n      item.json.markdown = '1. ' + extractMarkdown(item.json[type])\n    } else if(type == 'to_do') {\n      item.json.markdown = '+ [ ] ' + extractMarkdown(item.json[type])\n    } else if(type == 'image') {\n      item.json.markdown = '![image]('+item.json.image[item.json.image.type].url+')'\n    } else if(type == 'video') {\n      item.json.markdown = '[🎬 Video]('+$input.all()[0].json.page_url + '?pvs=4#' + item.json.id.replaceAll('-', '') +')'\n    } else {\n      item.json.markdown = extractMarkdown(item.json[type])\n    }\n  \n    // Figure out how much to indent it\n    // If parent ID is in list, remove everything after that ID\n    // If parent ID is not in list, add it\n    // If parent is the same, do nothing\n    const parent_id_index = parent_ids.indexOf(item.json.parent_id);\n  \n    // Check if the value is found\n    if (parent_id_index !== -1) {\n      // Remove all elements after the first occurrence\n      parent_ids.splice(parent_id_index + 1);\n    } else {\n      parent_ids.push(item.json.parent_id)\n    }\n  \n    // Indent the markdown\n    //if (type != \"image\") {\n      item.json.markdown = indent.repeat(parent_ids.length - 1) + item.json.markdown\n    //}\n  }\n}\n\n// On returning, add in the root block content at the beginning\nreturn [\n  ...[\n    {\n      \"json\": {\n        \"markdown\":\nextractMarkdown($input.all()[0].json.root_content)\n      },\n      \"pairedItem\": 0\n    }\n  ],\n  ...$input.all()\n]"
      },
      "typeVersion": 2
    },
    {
      "id": "2bbd43a2-aebe-4ee5-8682-d76791214168",
      "name": "错误响应",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -220,
        1580
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "{\n  \"formSubmittedText\": \"Couldn't fetch page content from Notion. Is it shared with your Notion integration?\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "c39912c4-d33f-4d79-9d5c-e71bd0f2517d",
      "name": "错误响应1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        -740,
        1580
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"formSubmittedText\": \"Couldn't find the team called '\" + {{ $('n8n Form Trigger').item.json['Linear team name'] }} + \"'\"\n} "
      },
      "typeVersion": 1
    }
  ],
  "pinData": {},
  "connections": {
    "Aggregate": {
      "main": [
        [
          {
            "node": "Prepare issue data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get issues": {
      "main": [
        [
          {
            "node": "Unimported, unchecked to_do blocks only",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond with error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set team ID": {
      "main": [
        [
          {
            "node": "Get issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set page URL": {
      "main": [
        [
          {
            "node": "Convert contents to Markdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get issue URL": {
      "main": [
        [
          {
            "node": "Add link to Notion block",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Shorten title": {
      "main": [
        [
          {
            "node": "Get issue contents",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Team missing?": {
      "main": [
        [
          {
            "node": "Respond with error1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set team ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        null,
        [
          {
            "node": "Set assignee and title",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "n8n Form Trigger": {
      "main": [
        [
          {
            "node": "Fetch Linear team details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get issue contents": {
      "main": [
        [
          {
            "node": "Set page URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare issue data": {
      "main": [
        [
          {
            "node": "Create linear issue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create linear issue": {
      "main": [
        [
          {
            "node": "Get issue URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set assignee and title": {
      "main": [
        [
          {
            "node": "Shorten title",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add link to Notion block": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Linear team details": {
      "main": [
        [
          {
            "node": "Team missing?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert contents to Markdown": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Unimported, unchecked to_do blocks only": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

高级 - 工程, 产品

需要付费吗?

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

工作流信息
难度等级
高级
节点数量24
分类2
节点类型14
难度说明

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

外部链接
在 n8n.io 查看

分享此工作流