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 response",
      "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 request",
      "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