8
n8n 한국어amn8n.com

Streamlit 애플리케이션에서 자동으로 Jira 작업 티켓 생성

고급

이것은Project Management분야의자동화 워크플로우로, 16개의 노드를 포함합니다.주로 If, Set, Code, Webhook, HttpRequest 등의 노드를 사용하며. Webhook과 REST API를 통해 Streamlit 양식에서 Jira 작업지 생성

사전 요구사항
  • HTTP Webhook 엔드포인트(n8n이 자동으로 생성)
  • 대상 API의 인증 정보가 필요할 수 있음
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
  "id": "2DxalJmssGiuQC9h",
  "meta": {
    "instanceId": "0430772da25f7bca29bf5ef2b251086a85fb4096503a6f781526d32befd038d6",
    "templateCredsSetupCompleted": true
  },
  "name": "Automate Jira Ticket Creation from Your Streamlit App",
  "tags": [],
  "nodes": [
    {
      "id": "26e7793d-e192-4075-8873-4c97fa2fe616",
      "name": "Webhook streamlit",
      "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": "## Required\n\n\n- Streamlit creation ticket app\n- Jira account"
      },
      "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) Trigger & intake (Streamlit → n8n)\n\nPurpose: receive the ticket from the app and hand it to the workflow.\n\nNodes\n\nWebhook streamlit – receives action=create_ticket and a ticket object (id, projectKey, type, summary, description, priority, story_points, due_date, labels…).\n\nraw data from streamlit (Set) – optional pass-through to visualize the raw payload in executions.\n\nSetup\n\nWebhook → Response mode: lastNode (so the final node’s response goes back to Streamlit).\n\nUse the Production URL in the app (avoid the Test URL in production).\n\nDon’t run the Jira HTTP node manually; always trigger through the 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) Create issue in Jira\n\nPurpose: call Jira REST API and create the real ticket.\n\nNodes\n\nProcessed data (Set) – passes through the fields object so you can inspect it.\n\nJira HTTP request (HTTP Request)\n\nMethod: POST\n\nURL: https://<your-domain>.atlassian.net/rest/api/3/issue\n\nAuth: Jira Software Cloud API (email + API token)\n\nHeaders: Content-Type: application/json (Accept optional)\n\nBody: Raw JSON = { \"fields\": … } from the previous node (not a stringified “[object Object]”).\n\nExpected output\n\nJira returns JSON with the new issue key (e.g., TES-123).\n"
      },
      "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) Normalize & build Jira payload\n\nPurpose: transform app fields into a valid Jira JSON.\n\nNodes\n\nProcess streamlit data (Code) – outputs a clean fields object:\n\nproject.key (uppercase from projectKey)\n\nissuetype.name (Task/Story/Bug/Epic)\n\nsummary\n\ndescription in Atlassian document format (doc → paragraph → text)\n\nOptional: priority.name, duedate (YYYY-MM-DD), labels (array)\n\nOptional: customfield_10016 for Story Points (or your instance’s ID)\n\nNotes\n\nDon’t set assignee.name in Jira Cloud; use assignee.accountId only if you have it."
      },
      "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) De-dup guard & branching (count/IF)\n\nPurpose: prevent empty/invalid calls and duplicates from creating issues.\n\nNodes\n\nanti double (Code) – builds a short-lived memory in workflow static data.\n\nIf ticket.id already seen (or fingerprint of projectKey+type+summary+description), mark as duplicate.\n\nIf action isn’t create_ticket or required fields are missing, mark as invalid.\n\nIF for doubles\n\nTrue branch (duplicate/invalid) → blocked request (Code) returns { ok: true, duplicate: true | ignored: true } to the Webhook and stops.\n\nFalse branch (clean request) → continue.\n\nValidation\n\nSubmit the same form twice quickly → True path should fire once (no second Jira issue).\n\n“Test connection” from the app should be ignored and not create anything."
      },
      "typeVersion": 1
    },
    {
      "id": "d933ae95-f6f6-4044-85de-f9a5fcca67e3",
      "name": "참고 사항5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1664,
        -704
      ],
      "parameters": {
        "height": 1264,
        "content": "## 5) Return result to the app\n\nPurpose: send a friendly response back to Streamlit.\n\nNodes\n\njira response (Set) – optional pass-through of the Jira response for visibility.\n\nResult (Code) – returns to the Webhook:\n{ ok: true, jiraKey: <KEY>, url: https://<domain>.atlassian.net/browse/<KEY> }\n\nUser experience\n\nStreamlit shows the created key and a link to open the ticket.\n\nMake sure to include your Jira space link in the Javascript code"
      },
      "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": {
    "04a874db-e6f5-45b0-9c24-c1353fbe82c4": {
      "main": [
        []
      ]
    },
    "f73b22ee-e14e-4d0c-a638-86c79c79b540": {
      "main": [
        [
          {
            "node": "ed82c023-8052-4431-81cc-1474cdd41d7d",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "e3d42140-21c8-414e-92bb-9b370db03cd8": {
      "main": [
        [
          {
            "node": "04a874db-e6f5-45b0-9c24-c1353fbe82c4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "08a64f5f-a115-492b-a74d-729a26e91964": {
      "main": [
        [
          {
            "node": "99fc511f-63a6-4bae-bd76-c71e7d8cd41f",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1d1a8b2d-6cbf-4844-9ca3-112d1786ae53": {
      "main": [
        [
          {
            "node": "08a64f5f-a115-492b-a74d-729a26e91964",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "eb9ab21e-f39e-4f79-b7e1-7ea092e2d8e7",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "eb9ab21e-f39e-4f79-b7e1-7ea092e2d8e7": {
      "main": [
        []
      ]
    },
    "99fc511f-63a6-4bae-bd76-c71e7d8cd41f": {
      "main": [
        [
          {
            "node": "e3d42140-21c8-414e-92bb-9b370db03cd8",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "26e7793d-e192-4075-8873-4c97fa2fe616": {
      "main": [
        [
          {
            "node": "f73b22ee-e14e-4d0c-a638-86c79c79b540",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "561cad41-bb3f-4c79-8040-dd8eea591ecf": {
      "main": [
        [
          {
            "node": "1d1a8b2d-6cbf-4844-9ca3-112d1786ae53",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ed82c023-8052-4431-81cc-1474cdd41d7d": {
      "main": [
        [
          {
            "node": "561cad41-bb3f-4c79-8040-dd8eea591ecf",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
자주 묻는 질문

이 워크플로우를 어떻게 사용하나요?

위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.

이 워크플로우는 어떤 시나리오에 적합한가요?

고급 - 프로젝트 관리

유료인가요?

이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.

워크플로우 정보
난이도
고급
노드 수16
카테고리1
노드 유형6
난이도 설명

고급 사용자를 위한 16+개 노드의 복잡한 워크플로우

저자
Yassin Zehar

Yassin Zehar

@yassinzehar

Digital & IT Project Manager | Data-oriented | Agile certified (PSM I, PSPO I) | Paris

외부 링크
n8n.io에서 보기

이 워크플로우 공유

카테고리

카테고리: 34