从 Streamlit 应用自动创建 Jira 工单
高级
这是一个Project Management领域的自动化工作流,包含 16 个节点。主要使用 If, Set, Code, Webhook, HttpRequest 等节点。 通过 Webhook 和 REST API 从 Streamlit 表单创建 Jira 工单
前置要求
- •HTTP Webhook 端点(n8n 会自动生成)
- •可能需要目标 API 的认证凭证
使用的节点 (16)
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "2DxalJmssGiuQC9h",
"meta": {
"instanceId": "0430772da25f7bca29bf5ef2b251086a85fb4096503a6f781526d32befd038d6",
"templateCredsSetupCompleted": true
},
"name": "从 Streamlit 应用自动创建 Jira 工单",
"tags": [],
"nodes": [
{
"id": "26e7793d-e192-4075-8873-4c97fa2fe616",
"name": "Streamlit Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-352,
16
],
"webhookId": "b8bb8a6a-5f41-4199-88f6-35b8937c4a82",
"parameters": {
"path": "your path here",
"options": {},
"httpMethod": "POST",
"responseMode": "lastNode"
},
"typeVersion": 2.1
},
{
"id": "e3d42140-21c8-414e-92bb-9b370db03cd8",
"name": "Jira 响应",
"type": "n8n-nodes-base.set",
"position": [
1488,
0
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{$json}}"
},
"typeVersion": 3.4
},
{
"id": "99fc511f-63a6-4bae-bd76-c71e7d8cd41f",
"name": "Jira HTTP 请求",
"type": "n8n-nodes-base.httpRequest",
"position": [
1248,
0
],
"parameters": {
"body": "={{ JSON.stringify($json) }}",
"method": "POST",
"options": {},
"sendBody": true,
"contentType": "raw",
"sendHeaders": true,
"rawContentType": "application/json",
"headerParameters": {
"parameters": [
{
"name": "=Content-Type",
"value": "=application/json"
},
{
"name": "Accept",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "ed82c023-8052-4431-81cc-1474cdd41d7d",
"name": "来自 Streamlit 的原始数据",
"type": "n8n-nodes-base.set",
"position": [
80,
16
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ $json }}"
},
"typeVersion": 3.4
},
{
"id": "561cad41-bb3f-4c79-8040-dd8eea591ecf",
"name": "处理 Streamlit 数据",
"type": "n8n-nodes-base.code",
"position": [
304,
16
],
"parameters": {
"jsCode": "const input = $json;\n\n// Journal to debug\nconsole.log(\"=== DATA RECEIVED ===\");\nconsole.log(JSON.stringify(input, null, 2));\n\n// stop if request empty\nconst quickCheck = input.body?.ticket?.summary || input.ticket?.summary || input.summary;\nif (!quickCheck || quickCheck.toString().trim() === \"\") {\n console.log(\"ARRÊT : Empty request detected\");\n return [{ json: { blocked: true, reason: \"empty_request\" } }];\n}\n\n// check: stop if no useful data\nif (!input || Object.keys(input).length === 0) {\n console.log(\"ARRÊT : No data received\");\n return [{ json: { blocked: true, reason: \"no_data\" } }];\n}\n\n// Extraction\nconst getValue = (obj, keys, defaultValue = null) => {\n for (const key of keys) {\n if (obj && obj[key] !== undefined && obj[key] !== null && obj[key] !== \"\") {\n return obj[key];\n }\n }\n return defaultValue;\n};\n\n// In case if data are in input.ticket\nconst ticketData = input.ticket || input.body?.ticket || input;\n\n// Extract with strict validation\nconst projectKey = getValue(ticketData, ['projectKey', 'project'], \"TES\");\nconst summary = getValue(ticketData, ['summary', 'title', 'name']);\nconst type = getValue(ticketData, ['type', 'issuetype'], \"Task\");\nconst description = getValue(ticketData, ['description', 'desc']);\nconst priority = getValue(ticketData, ['priority', 'severity']);\nconst storyPoints = getValue(ticketData, ['story_points', 'storyPoints', 'points']);\nconst assignee = getValue(ticketData, ['assignee', 'assignedTo', 'owner']);\n\n// critical validation, stop if summary is empty\nif (!summary || summary.trim() === \"\" || summary === \"Ticket without title\") {\n console.log(\"ARRÊT : Empty Summary or by default detected\");\n return [{ json: { blocked: true, reason: \"no_summary\" } }];\n}\n\n// critical validation, stop if description is empty\nif (!description || description.trim() === \"\" || description === \"Aucune description fournie\") {\n console.log(\"STOP : Empty Description or by default detected\");\n return [{ json: { blocked: true, reason: \"no_description\" } }];\n}\n\nconsole.log(\"=== Values Validated ===\");\nconsole.log(JSON.stringify({\n projectKey,\n summary,\n type,\n description,\n priority,\n storyPoints,\n assignee\n}, null, 2));\n\n// payload only if data are ok\nconst payload = {\n fields: {\n project: { key: projectKey.toString().toUpperCase() },\n summary: summary.toString().trim(),\n issuetype: { name: type.toString() },\n description: {\n type: \"doc\",\n version: 1,\n content: [{\n type: \"paragraph\",\n content: [{ type: \"text\", text: description.toString().trim() }]\n }]\n }\n }\n};\n\n\nif (priority && priority !== \"null\" && priority.trim() !== \"\") {\n payload.fields.priority = { name: priority.toString().trim() };\n}\n\n// Story points : ID can change depending on Jira\nif (storyPoints !== null && storyPoints !== undefined && !isNaN(parseInt(storyPoints))) {\npayload.fields.customfield_10016 = parseInt(storyPoints);\n}\n\n// Assignee deleted : needs accountId in Jira Cloud, not 'name'\n// if (assignee && assignee.trim() !== \"\") {\n// payload.fields.assignee = { name: assignee.toString().trim() };\n// }\n\nconsole.log(\"=== FINAL PAYLOAD VALIDATED ===\");\nconsole.log(JSON.stringify(payload, null, 2));\n\nreturn [{ json: payload }];"
},
"typeVersion": 2
},
{
"id": "08a64f5f-a115-492b-a74d-729a26e91964",
"name": "已处理数据",
"type": "n8n-nodes-base.set",
"position": [
928,
0
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "={{ $json }}"
},
"typeVersion": 3.4
},
{
"id": "04a874db-e6f5-45b0-9c24-c1353fbe82c4",
"name": "结果",
"type": "n8n-nodes-base.code",
"position": [
1744,
0
],
"parameters": {
"jsCode": "return [{ json: {\n ok: true,\n jiraKey: $json.key,\n url: `https://YOURJIRAURL.atlassian.net/browse/${$json.key}`\n}}];\n"
},
"typeVersion": 2
},
{
"id": "f73b22ee-e14e-4d0c-a638-86c79c79b540",
"name": "防重复",
"type": "n8n-nodes-base.code",
"position": [
-144,
16
],
"parameters": {
"jsCode": "// Block empty request\nconst hasValidSummary = $json.body?.ticket?.summary || $json.ticket?.summary || $json.summary;\n\nif (!hasValidSummary || hasValidSummary.trim() === \"\") {\n console.log(\"Empty request detected - blocked\");\n return [{ json: { blocked: true, reason: \"empty_request\" } }];\n}\n\n// allow valid data\nreturn items;"
},
"typeVersion": 2
},
{
"id": "eb9ab21e-f39e-4f79-b7e1-7ea092e2d8e7",
"name": "被阻止的请求",
"type": "n8n-nodes-base.code",
"position": [
624,
416
],
"parameters": {
"jsCode": "return [{ json: { ok: false, message: \"Empty request blocked\" } }];"
},
"typeVersion": 2
},
{
"id": "1d1a8b2d-6cbf-4844-9ca3-112d1786ae53",
"name": "重复判断条件",
"type": "n8n-nodes-base.if",
"position": [
496,
16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "b785d1c2-020d-4456-87cc-9d2389a30635",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "={{ $json.blocked }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "25ce6d92-a06e-46fc-9cd4-61a3a598b9ab",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
-704
],
"parameters": {
"color": 3,
"width": 304,
"height": 176,
"content": "## 必需条件\\n\\n- Streamlit 创建工单应用\\n- Jira 账户"
},
"typeVersion": 1
},
{
"id": "f81324d8-fd8e-485a-8398-72768c64ff09",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
-704
],
"parameters": {
"color": 2,
"width": 416,
"height": 1264,
"content": "## 1) 触发器与数据接收 (Streamlit → n8n)\\n\\n目的:从应用接收工单并传递给工作流。\\n\\n节点\\n\\nStreamlit Webhook – 接收 action=create_ticket 和工单对象(id、projectKey、type、summary、description、priority、story_points、due_date、labels…)。\\n\\n来自 Streamlit 的原始数据(设置节点)– 可选透传,用于在执行中可视化原始负载。\\n\\n设置\\n\\nWebhook → 响应模式:lastNode(这样最终节点的响应会返回给 Streamlit)。\\n\\n在应用中使用生产 URL(避免在生产环境中使用测试 URL)。\\n\\n不要手动运行 Jira HTTP 节点;始终通过 Webhook 触发。"
},
"typeVersion": 1
},
{
"id": "5e0bc57b-223c-46fe-bd90-5682ef4c5e67",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1152,
-704
],
"parameters": {
"color": 4,
"width": 464,
"height": 1264,
"content": "## 4) 在 Jira 中创建问题\\n\\n目的:调用 Jira REST API 并创建实际工单。\\n\\n节点\\n\\n已处理数据(设置节点)– 透传字段对象以便检查。\\n\\nJira HTTP 请求(HTTP 请求节点)\\n\\n方法:POST\\n\\nURL:https://<你的域名>.atlassian.net/rest/api/3/issue\\n\\n认证:Jira Software Cloud API(邮箱 + API 令牌)\\n\\n请求头:Content-Type: application/json(Accept 可选)\\n\\n请求体:原始 JSON = { \"fields\": … } 来自前一个节点(不是字符串化的 \"[object Object]\")。\\n\\n预期输出\\n\\nJira 返回包含新问题键的 JSON(例如 TES-123)。"
},
"typeVersion": 1
},
{
"id": "5175079e-7ddc-46f1-bf93-532421c2e16a",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
816,
-704
],
"parameters": {
"color": 5,
"width": 304,
"height": 1264,
"content": "## 3) 规范化并构建 Jira 负载\\n\\n目的:将应用字段转换为有效的 Jira JSON。\\n\\n节点\\n\\n处理 Streamlit 数据(代码节点)– 输出干净的字段对象:\\n\\nproject.key(来自 projectKey 的大写)\\n\\nissuetype.name(Task/Story/Bug/Epic)\\n\\nsummary\\n\\nAtlassian 文档格式的 description(doc → paragraph → text)\\n\\n可选:priority.name、duedate(YYYY-MM-DD)、labels(数组)\\n\\n可选:Story Points 的 customfield_10016(或您实例的 ID)\\n\\n注意事项\\n\\n不要在 Jira Cloud 中设置 assignee.name;仅当您拥有 assignee.accountId 时才使用它。"
},
"typeVersion": 1
},
{
"id": "953b841a-8814-4047-bce8-a8644d266005",
"name": "便签说明4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-176,
-704
],
"parameters": {
"color": 6,
"width": 944,
"height": 1264,
"content": "## 2) 去重防护与分支(计数/条件判断)\\n\\n目的:防止空/无效调用和重复创建问题。\\n\\n节点\\n\\n防重复(代码节点)– 在工作流静态数据中构建短期内存。\\n\\n如果 ticket.id 已存在(或 projectKey+type+summary+description 的指纹),标记为重复。\\n\\n如果 action 不是 create_ticket 或必填字段缺失,标记为无效。\\n\\n重复判断条件\\n\\n真分支(重复/无效)→ 被阻止的请求(代码节点)返回 { ok: true, duplicate: true | ignored: true } 给 Webhook 并停止。\\n\\n假分支(干净请求)→ 继续。\\n\\n验证\\n\\n快速提交相同表单两次 → 真路径应触发一次(不会创建第二个 Jira 问题)。\\n\\n来自应用的“测试连接”应被忽略且不创建任何内容。"
},
"typeVersion": 1
},
{
"id": "d933ae95-f6f6-4044-85de-f9a5fcca67e3",
"name": "便签说明5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1664,
-704
],
"parameters": {
"height": 1264,
"content": "## 5) 将结果返回给应用\\n\\n目的:向 Streamlit 返回友好的响应。\\n\\n节点\\n\\nJira 响应(设置节点)– 可选透传 Jira 响应以便查看。\\n\\n结果(代码节点)– 返回给 Webhook:\\n{ ok: true, jiraKey: <KEY>, url: https://<域名>.atlassian.net/browse/<KEY> }\\n\\n用户体验\\n\\nStreamlit 显示创建的键和打开工单的链接。\\n\\n确保在 Javascript 代码中包含您的 Jira 空间链接"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {
"Webhook streamlit": [
{
"json": {
"body": {
"action": "ping"
},
"query": {},
"params": {},
"headers": {
"host": "localhost:5678",
"accept": "*/*",
"connection": "keep-alive",
"user-agent": "python-requests/2.32.5",
"content-type": "application/json",
"content-length": "18",
"accept-encoding": "gzip, deflate"
},
"webhookUrl": "http://localhost:5678/webhook/19e7046a-ed85-4f0f-a54f-bc190e889257",
"executionMode": "production"
}
}
]
},
"settings": {
"executionOrder": "v1"
},
"versionId": "10641e4a-3357-4aeb-939e-c22ae7a73db2",
"connections": {
"Result": {
"main": [
[]
]
},
"anti double": {
"main": [
[
{
"node": "raw data from streamlit",
"type": "main",
"index": 0
}
]
]
},
"jira response": {
"main": [
[
{
"node": "Result",
"type": "main",
"index": 0
}
]
]
},
"Processed data": {
"main": [
[
{
"node": "Jira HTTP request",
"type": "main",
"index": 0
}
]
]
},
"if for doubles": {
"main": [
[
{
"node": "Processed data",
"type": "main",
"index": 0
}
],
[
{
"node": "blocked request",
"type": "main",
"index": 0
}
]
]
},
"blocked request": {
"main": [
[]
]
},
"Jira HTTP request": {
"main": [
[
{
"node": "jira response",
"type": "main",
"index": 0
}
]
]
},
"Webhook streamlit": {
"main": [
[
{
"node": "anti double",
"type": "main",
"index": 0
}
]
]
},
"Process streamlit data": {
"main": [
[
{
"node": "if for doubles",
"type": "main",
"index": 0
}
]
]
},
"raw data from streamlit": {
"main": [
[
{
"node": "Process streamlit data",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 项目管理
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
转录 -> Notion -> 任务
使用AI和Google Drive将会议转录转换为Notion笔记和任务
If
Set
Code
+12
36 节点Matty Reed
项目管理
我的工作流10
带有Telegram通知和Supabase数据库的Trello任务管理
If
Set
Code
+8
30 节点Hermon
项目管理
基于蓝图和Baserow的自动化任务生成与周末感知调度
基于蓝图和Baserow的自动化任务生成与周末感知调度
Set
Code
Baserow
+5
12 节点Frederik Duchi
项目管理
Telegram AI歌词学习机器人 — 翻译、摘要、词汇
Telegram AI歌词学习机器人 — 翻译、摘要、词汇
If
Set
Code
+7
30 节点Raphael De Carvalho Florencio
内容创作
WhatsApp上的AI电影推荐器
WhatsApp上的AI电影推荐器
If
Set
Code
+5
20 节点Oneclick AI Squad
客服机器人
AI房地产经纪人:端到端运营自动化(网络、数据、语音)
AI房地产经纪人:端到端运营自动化(网络、数据、语音)
If
Set
Code
+16
45 节点Sam Yassine
销售
工作流信息
难度等级
高级
节点数量16
分类1
节点类型6
作者
Yassin Zehar
@yassinzeharDigital & IT Project Manager | Data-oriented | Agile certified (PSM I, PSPO I) | Paris
外部链接
在 n8n.io 查看 →
分享此工作流