AutomationFlowsWeb Scraping › Sync Tuleap Bugs to QC-Manager

Sync Tuleap Bugs to QC-Manager

Original n8n title: Tuleap Bug → Qc-manager Sync

Tuleap Bug → QC-Manager Sync. Uses httpRequest. Webhook trigger; 10 nodes.

Webhook trigger★★★★☆ complexity10 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 10 Complexity: ★★★★☆ Added:

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "BugSync001TuleapQC",
  "name": "Tuleap Bug \u2192 QC-Manager Sync",
  "active": true,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "tuleap-bug",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "b1234567-0001-4001-8001-000000000001",
      "name": "Webhook: Tuleap Bug",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "// \u2500\u2500 FIXED: Handle both Tuleap webhook formats \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Format A (legacy): application/x-www-form-urlencoded, body = { payload: '<JSON string>' }\n// Format B (current): application/json, body = { artifact, project, user }\n\nconst raw = $input.first().json;\n// In n8n v1+ with typeVersion 2, form fields are hoisted; check both locations\nconst body = (raw.body !== undefined && typeof raw.body === 'object') ? raw.body : raw;\n\nlet payload;\n\nif (body.payload !== undefined) {\n  // Format A: payload key contains a JSON string (or already-parsed object)\n  payload = typeof body.payload === 'string' ? JSON.parse(body.payload) : body.payload;\n  // Validate the parsed payload has the expected shape\n  if (!payload || typeof payload !== 'object') {\n    throw new Error('Parsed payload is not a valid object');\n  }\n} else if (body.artifact !== undefined) {\n  // Format B: direct JSON \u2014 normalize to the same internal shape\n  payload = {\n    current: body.artifact,\n    project: body.project || null,\n    user: body.user || null,\n    action: body.action || 'artifact:created'\n  };\n} else {\n  const keys = Object.keys(body).join(', ') || '(empty body)';\n  throw new Error(`Unrecognized Tuleap webhook format. Body keys: ${keys}. Check Tuleap webhook configuration.`);\n}\n\n// Validate the artifact and tracker exist\nconst tracker = payload.tracker || payload.current?.tracker;\nif (!tracker?.id) {\n  throw new Error(\n    `Missing tracker.id in payload. ` +\n    `tracker = ${JSON.stringify(tracker)}`\n  );\n}\n\nreturn [{ json: { payload } }];"
      },
      "id": "b1234567-0002-4002-8002-000000000002",
      "name": "Parse Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        304
      ]
    },
    {
      "parameters": {
        "url": "http://qc-api:3001/tuleap-webhook/config",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "tracker_type",
              "value": "bug"
            },
            {
              "name": "is_active",
              "value": "true"
            }
          ]
        },
        "options": {}
      },
      "id": "b1234567-0003-4003-8003-000000000003",
      "name": "Fetch Sync Config",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        720,
        304
      ]
    },
    {
      "parameters": {
        "jsCode": "// Find the sync config matching this tracker ID\nconst payload = $('Parse Payload').first().json.payload;\nconst configs = $('Fetch Sync Config').first().json.data || [];\nconst trackerId = payload?.tracker?.id;\n\nconst config = configs.find(c => String(c.tuleap_tracker_id) === String(trackerId));\nif (!config) {\n  return [{ json: { hasConfig: false, trackerId, payload } }];\n}\nreturn [{ json: { hasConfig: true, config, payload } }];"
      },
      "id": "b1234567-0004-4004-8004-000000000004",
      "name": "Match Config",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        304
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-config-check",
              "leftValue": "={{ $json.hasConfig }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals",
                "rightType": "boolean"
              }
            }
          ]
        },
        "options": {}
      },
      "id": "b1234567-0005-4005-8005-000000000005",
      "name": "Has Config?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1200,
        304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://qc-api:3001/tuleap-webhook/project",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ project_id: $('Match Config').first().json.payload.project?.id, name: $('Match Config').first().json.payload.project?.label || ('Tuleap Project ' + String($('Match Config').first().json.payload.project?.id || 'unknown')), path: $('Match Config').first().json.payload.project?.shortname || null }) }}",
        "options": {
          "timeout": 15000
        }
      },
      "id": "b1234567-0010-4010-8010-000000000010",
      "name": "Provision Project",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        464
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build a synthetic config from the auto-provisioned QC project\nconst parsed  = $('Match Config').first().json;\nconst apiResp = $input.first().json;\n\nconst qcProject = apiResp.data;\nif (!qcProject?.id) {\n  throw new Error(`Project provisioning failed. API response: ${JSON.stringify(apiResp).slice(0, 300)}`);\n}\n\nconst config = {\n  qc_project_id:     qcProject.id,\n  qc_project_name:   qcProject.project_name || null,\n  tuleap_project_id: parsed.payload?.project?.id || null,\n  tuleap_base_url:   null,\n  status_mappings:   {}\n};\n\nreturn [{ json: { hasConfig: true, config, payload: parsed.payload } }];"
      },
      "id": "b1234567-0011-4011-8011-000000000011",
      "name": "Build Config from Provision",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        464
      ]
    },
    {
      "parameters": {
        "jsCode": "const { config, payload } = $input.first().json;\nconst artifact = payload.current;\nconst statusMappings = config.status_mappings || {};\n\nconst fields = {};\nfor (const v of (artifact.values || [])) { fields[v.label] = v; }\n\nfunction txt(...labels) {\n  for (const lbl of labels) {\n    const f = fields[lbl];\n    if (!f) continue;\n    if (f.value !== undefined && f.value !== null) return String(f.value);\n  }\n  return null;\n}\n\nfunction sel(...labels) {\n  for (const lbl of labels) {\n    const f = fields[lbl];\n    if (!f) continue;\n    if (f.values && f.values[0]) return f.values[0].label || f.values[0].display_name || String(f.values[0]);\n  }\n  return null;\n}\n\nfunction user(...labels) {\n  for (const lbl of labels) {\n    const f = fields[lbl];\n    if (!f || !f.values) continue;\n    const u = f.values[0];\n    if (u) return u.display_name || u.real_name || u.username || null;\n  }\n  return null;\n}\n\nfunction mapStatus(s) {\n  if (!s) return 'Open';\n  return statusMappings[s] || { New:'Open', Open:'open', Assigned:'In Progress',\n    Fixed:'Resolved', Resolved:'Resolved', Closed:'Closed',\n    Rejected:'Closed', Reopened:'Reopened' }[s] || 'Open';\n}\n\nfunction mapSeverity(s) {\n  if (!s) return 'medium';\n  const lower = s.toLowerCase();\n  if (lower.includes('critical')) return 'critical';\n  if (lower.includes('major'))     return 'high';\n  if (lower.includes('minor'))     return 'medium';\n  if (lower.includes('cosmetic'))  return 'low';\n  if (lower.includes('high'))      return 'high';\n  if (lower.includes('medium'))    return 'medium';\n  if (lower.includes('normal'))    return 'medium';\n  if (lower.includes('low'))       return 'low';\n  return 'medium';\n}\n\nconst artifactId = payload.id || artifact.id;\nconst allValues = artifact.values || [];\n\n// Check if bug has links to Test Case trackers via art_link fields\nconst hasTestCaseLink = allValues.some(v =>\n  v.type === 'art_link' &&\n  (\n    (Array.isArray(v.links) &&\n      v.links.some(link => link.tracker && link.tracker.label === 'Test Case')) ||\n    (Array.isArray(v.reverse_links) &&\n      v.reverse_links.some(link => link.tracker && link.tracker.label === 'Test Case'))\n  )\n);\n\nconst source = hasTestCaseLink ? 'TEST_CASE' : 'EXPLORATORY';\n\nconst bugData = {\n  tuleap_artifact_id: artifactId,\n  tuleap_tracker_id:  payload.tracker?.id,\n  tuleap_url: config.tuleap_base_url ? `${config.tuleap_base_url}/plugins/tracker/?aid=${artifactId}` : null,\n  bug_id:       `TLP-${artifactId}`,\n  title:        txt('Bug Title','Summary','Title','summary','title') || `Bug ${artifactId}`,\n  description:  txt('Description + Steps to reproduce','Description','Details','Original Submission','description'),\n  status:       mapStatus(sel('Status','status')),\n  severity:     mapSeverity(sel('Severity','severity','Importance')),\n  priority:     (sel('Priority','priority') || 'medium').toLowerCase(),\n  bug_type:     sel('Type','Category','Bug Type','type'),\n  component:    sel('Component','Module','component'),\n  project_id:   config.qc_project_id,\n  linked_test_case_ids: [],\n  linked_test_execution_ids: [],\n  reported_by:  payload.user?.display_name || null,\n  updated_by:   payload.user?.display_name || null,\n  assigned_to:  user('Assigned to','Assigned To','assigned_to'),\n  reported_date: artifact.submitted_on || null,\n  raw_tuleap_payload: payload,\n  source: source,\n  submitted_by_email: artifact.submitted_by?.email ?? null,\n  submitted_by_username: artifact.submitted_by?.username ?? null\n};\n\nreturn [{ json: { bugData } }];"
      },
      "id": "b1234567-0006-4006-8006-000000000006",
      "name": "Transform Bug Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1920,
        304
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://qc-api:3001/tuleap-webhook/bug",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.bugData) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "b1234567-0007-4007-8007-000000000007",
      "name": "Send to QC API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        304
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, message: 'Bug processed', bug_id: $json.data?.id || 'unknown' }) }}",
        "options": {}
      },
      "id": "b1234567-0008-4008-8008-000000000008",
      "name": "Respond: OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        2400,
        304
      ]
    }
  ],
  "connections": {
    "Webhook: Tuleap Bug": {
      "main": [
        [
          {
            "node": "Parse Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Payload": {
      "main": [
        [
          {
            "node": "Fetch Sync Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Sync Config": {
      "main": [
        [
          {
            "node": "Match Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Config": {
      "main": [
        [
          {
            "node": "Has Config?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Config?": {
      "main": [
        [
          {
            "node": "Transform Bug Data",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Provision Project",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Provision Project": {
      "main": [
        [
          {
            "node": "Build Config from Provision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Config from Provision": {
      "main": [
        [
          {
            "node": "Transform Bug Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transform Bug Data": {
      "main": [
        [
          {
            "node": "Send to QC API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send to QC API": {
      "main": [
        [
          {
            "node": "Respond: OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    {
      "name": "tuleap"
    }
  ]
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Tuleap Bug → QC-Manager Sync. Uses httpRequest. Webhook trigger; 10 nodes.

Source: https://github.com/Gebrilo/QC-Manager/blob/48bdcc4c151775e3e075bfe1a53fb58cd5945d9d/n8n-workflows/tuleap-bug-sync.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a

HTTP Request