SolarWinds Service Desk - Last 100 Incidents to Database

    This N8N workflow collects the last 100 incidents from SolarWinds Service Desk API and stores them in a SQL Server database for reporting/dashboard purposes. Workflow Overview: Triggers: Scheduled execution every hour Manual trigger for on-demand runs Webhook trigger Main Process: API Configuration - Sets SolarWinds Service Desk API base URL Fetch Incidents - Retrieves first 100 incidents via HTTP request with custom headers Truncate Tables - Clears both solarwinds_incidents and solarwinds_summary tables Build Incidents Insert - Transforms API response into SQL INSERT statements with: Incident details (ID, number, title, description) State mapping (Open/In Progress/Resolved) Priority, category, subcategory Assignee, requester, department info Timestamps and resolution data Insert Incidents - Executes batched SQL inserts (500 rows per batch) Build Summary Insert - Calculates statistics (total, open, in progress, resolved, priority counts) Insert Summary - Stores summary data

    Shared 12/23/2025

    4 views

    Visual Workflow

    JSON Code

    {
      "id": "phLhFkIrLiPbB9qa",
      "meta": {
        "instanceId": "f5498a3694b22d168149cbccf75115a48b85efb0858a1c814a63a2bf03cf6cd1",
        "templateCredsSetupCompleted": true
      },
      "name": "SolarWinds Incident Collector - Last 100 Tickets",
      "tags": [],
      "nodes": [
        {
          "id": "df9d3719-e892-43c6-95fa-ef5082132192",
          "name": "Schedule Every Hour",
          "type": "n8n-nodes-base.scheduleTrigger",
          "notes": "Runs automatically every hour",
          "position": [
            9776,
            1760
          ],
          "parameters": {
            "rule": {
              "interval": [
                {
                  "field": "hours"
                }
              ]
            }
          },
          "typeVersion": 1.1
        },
        {
          "id": "61f7691f-952d-4a8d-bafa-5a42fa0920ee",
          "name": "Manual Trigger",
          "type": "n8n-nodes-base.manualTrigger",
          "notes": "Click 'Execute Workflow' to run on-demand",
          "position": [
            9776,
            1952
          ],
          "parameters": {},
          "typeVersion": 1
        },
        {
          "id": "e9bd67a2-b704-46c0-a504-f05dd2c7f9da",
          "name": "Config - Base URL",
          "type": "n8n-nodes-base.code",
          "notes": "SolarWinds Service Desk API configuration",
          "position": [
            10000,
            1760
          ],
          "parameters": {
            "jsCode": "// SolarWinds Service Desk API base URL\nconst BASE_URL = 'https://api.samanage.com';\n\nreturn [{\n  json: {\n    incidents_url: `${BASE_URL}/incidents.json`\n  }\n}];"
          },
          "typeVersion": 2
        },
        {
          "id": "0a162b61-0750-42a7-a903-4d0e6c1ef0d6",
          "name": "Get Incidents Page 1",
          "type": "n8n-nodes-base.httpRequest",
          "notes": "Fetch first 100 incidents",
          "position": [
            10224,
            1760
          ],
          "parameters": {
            "url": "={{ $json.incidents_url }}?per_page=100&page=1",
            "options": {
              "timeout": 60000
            },
            "sendHeaders": true,
            "authentication": "genericCredentialType",
            "genericAuthType": "httpHeaderAuth",
            "headerParameters": {
              "parameters": [
                {
                  "name": "Accept",
                  "value": "application/vnd.samanage.v2.1+json"
                }
              ]
            }
          },
          "credentials": {
            "httpHeaderAuth": {
              "id": "hKX96Jn4RH7DVcpg",
              "name": "Solarwinds Service Desk"
            }
          },
          "typeVersion": 4.1
        },
        {
          "id": "a6c34627-0136-4a4a-a05b-213b930bfc78",
          "name": "Truncate Tables",
          "type": "n8n-nodes-base.microsoftSql",
          "position": [
            10448,
            1760
          ],
          "parameters": {
            "query": "TRUNCATE TABLE solarwinds_incidents;\nTRUNCATE TABLE solarwinds_summary;",
            "operation": "executeQuery"
          },
          "credentials": {
            "microsoftSql": {
              "id": "cQ8zhPt5LM1BWGFY",
              "name": "Microsoft SQL account 2"
            }
          },
          "typeVersion": 1.1
        },
        {
          "id": "9fb6774b-57db-43a1-bd4f-f5ada499db93",
          "name": "Build Incidents Insert",
          "type": "n8n-nodes-base.code",
          "position": [
            10672,
            1760
          ],
          "parameters": {
            "jsCode": "// FIXED - Build SQL INSERT handling n8n's item-by-item response\nconst timestamp = new Date().toISOString();\n\nfunction escapeSql(str) {\n  if (str === null || str === undefined) return 'NULL';\n  return \"'\" + String(str).replace(/'/g, \"''\").replace(/[\\r\\n]+/g, ' ').substring(0, 4000) + \"'\";\n}\n\n// n8n returns each API item separately - use .all() to get ALL items\nconst allItems = $('Get Incidents Page 1').all();\n\nconsole.log('\\n=== SOLARWINDS INCIDENTS PROCESSING ===');\nconsole.log(`Total items from n8n: ${allItems.length}`);\n\nif (allItems.length === 0) {\n  console.log('❌ No items returned from Get Incidents Page 1');\n  return [{json: {query: 'SELECT 1;', count: 0, message: 'No incidents'}}];\n}\n\n// Extract the json from each item\nconst incidents = allItems.map(item => item.json);\n\nconsole.log(`\\n✅ Extracted ${incidents.length} incidents`);\nconsole.log('\\n=== FIRST INCIDENT SAMPLE ===');\nconsole.log(JSON.stringify(incidents[0], null, 2).substring(0, 500));\n\nconst insertValues = [];\n\nfor (const incident of incidents) {\n  const incidentId = escapeSql(incident.id || '');\n  const incidentNumber = escapeSql(incident.number || '');\n  const title = escapeSql(incident.name || 'No Title');\n  const description = escapeSql(incident.description_no_html || incident.description || '');\n  \n  // State mapping\n  const rawState = (incident.state || 'Open').toLowerCase();\n  let state = 'Open';\n  if (rawState === 'resolved' || rawState === 'closed') state = 'Resolved';\n  else if (rawState === 'in progress' || rawState === 'assigned' || rawState === 'on hold') state = 'In Progress';\n  const stateEscaped = escapeSql(state);\n  \n  // Priority handling\n  let priorityName = 'Medium';\n  if (incident.priority) {\n    if (typeof incident.priority === 'object' && incident.priority.name) {\n      priorityName = incident.priority.name;\n    } else if (typeof incident.priority === 'string') {\n      priorityName = incident.priority;\n    }\n  }\n  const priority = escapeSql(priorityName);\n  \n  // Category handling\n  let categoryName = 'General';\n  if (incident.category) {\n    if (typeof incident.category === 'object' && incident.category.name) {\n      categoryName = incident.category.name;\n    } else if (typeof incident.category === 'string') {\n      categoryName = incident.category;\n    }\n  }\n  const category = escapeSql(categoryName);\n  \n  // Subcategory handling\n  let subcategoryName = '';\n  if (incident.subcategory) {\n    if (typeof incident.subcategory === 'object' && incident.subcategory.name) {\n      subcategoryName = incident.subcategory.name;\n    } else if (typeof incident.subcategory === 'string') {\n      subcategoryName = incident.subcategory;\n    }\n  }\n  const subcategory = escapeSql(subcategoryName);\n  \n  // Assignee\n  const assignee = escapeSql(incident.assignee?.name || '');\n  \n  // Requester\n  const requester = escapeSql(incident.requester?.name || 'Unknown');\n  \n  // Department\n  const department = escapeSql(incident.department?.name || '');\n  \n  // Timestamps\n  const createdAt = escapeSql(incident.created_at || timestamp);\n  const updatedAt = escapeSql(incident.updated_at || timestamp);\n  const dueAt = incident.due_at ? escapeSql(incident.due_at) : 'NULL';\n  \n  // Resolved info\n  const isResolved = (incident.resolved_by && incident.resolved_by.name) ? 1 : 0;\n  const resolvedBy = (incident.resolved_by?.name) ? escapeSql(incident.resolved_by.name) : 'NULL';\n  \n  // Portal URL\n  let portalUrl = '';\n  if (incident.id) {\n    portalUrl = `https://api.samanage.com/incidents/${incident.id}`;\n  } else if (incident.href_account_domain) {\n    portalUrl = incident.href_account_domain;\n  }\n  const portalUrlEscaped = escapeSql(portalUrl);\n  \n  insertValues.push(\n    `(${incidentId}, ${incidentNumber}, ${title}, ${description}, ${stateEscaped}, ${priority}, ${category}, ${subcategory}, ${assignee}, ${requester}, ${department}, ${createdAt}, ${updatedAt}, ${dueAt}, ${isResolved}, ${resolvedBy}, ${portalUrlEscaped}, ${escapeSql(timestamp)})`\n  );\n}\n\nconsole.log(`\\n✅ Prepared ${insertValues.length} incidents for SQL insertion`);\n\nif (insertValues.length === 0) {\n  return [{json: {query: 'SELECT 1;', count: 0}}];\n}\n\n// Batch the inserts (SQL Server handles ~1000 rows per INSERT)\nconst batchSize = 500;\nconst batches = [];\nfor (let i = 0; i < insertValues.length; i += batchSize) {\n  batches.push(insertValues.slice(i, i + batchSize));\n}\n\nconst queries = batches.map(batch => {\n  return `INSERT INTO solarwinds_incidents (incident_id, incident_number, title, description, state, priority, category, subcategory, assignee, requester, department, created_at, updated_at, due_at, is_resolved, resolved_by, portal_url, last_updated) VALUES\\n${batch.join(',\\n')};`;\n});\n\nconst fullQuery = queries.join('\\n\\n');\n\nconsole.log(`Inserting ${insertValues.length} incidents in ${batches.length} batch(es)`);\nconsole.log('\\n=== SQL QUERY PREVIEW (first 300 chars) ===');\nconsole.log(fullQuery.substring(0, 300) + '...');\n\nreturn [{json: {\n  query: fullQuery,\n  count: insertValues.length,\n  batchCount: batches.length\n}}];"
          },
          "typeVersion": 2
        },
        {
          "id": "c5133e6f-b947-406c-b4a7-8cecd342ea1e",
          "name": "Insert Incidents",
          "type": "n8n-nodes-base.microsoftSql",
          "position": [
            10896,
            1760
          ],
          "parameters": {
            "query": "={{ $json.query }}",
            "operation": "executeQuery"
          },
          "credentials": {
            "microsoftSql": {
              "id": "cQ8zhPt5LM1BWGFY",
              "name": "Microsoft SQL account 2"
            }
          },
          "typeVersion": 1.1
        },
        {
          "id": "d78b947d-33d6-4e34-a5b0-9011e3e26841",
          "name": "Build Summary Insert",
          "type": "n8n-nodes-base.code",
          "position": [
            11120,
            1760
          ],
          "parameters": {
            "jsCode": "// Calculate summary statistics - FIXED to use .all()\nconst allItems = $('Get Incidents Page 1').all();\nconst incidents = allItems.map(item => item.json);\nconst timestamp = new Date().toISOString();\n\nfunction escapeSql(str) {\n  if (str === null || str === undefined) return 'NULL';\n  return \"'\" + String(str).replace(/'/g, \"''\") + \"'\";\n}\n\nconsole.log(`\\n=== CALCULATING SUMMARY ===`);\nconsole.log(`Total incidents: ${incidents.length}`);\n\nlet totalIncidents = 0;\nlet openIncidents = 0;\nlet inProgressIncidents = 0;\nlet resolvedIncidents = 0;\n\nlet criticalPriority = 0;\nlet highPriority = 0;\nlet mediumPriority = 0;\nlet lowPriority = 0;\n\nfor (const incident of incidents) {\n  totalIncidents++;\n  \n  // State counting\n  const rawState = (incident.state || 'Open').toLowerCase();\n  if (rawState === 'resolved' || rawState === 'closed') {\n    resolvedIncidents++;\n  } else if (rawState === 'in progress' || rawState === 'assigned' || rawState === 'on hold') {\n    inProgressIncidents++;\n  } else {\n    openIncidents++;\n  }\n  \n  // Priority counting\n  let priorityName = 'Medium';\n  if (incident.priority) {\n    if (typeof incident.priority === 'object' && incident.priority.name) {\n      priorityName = incident.priority.name;\n    } else if (typeof incident.priority === 'string') {\n      priorityName = incident.priority;\n    }\n  }\n  \n  const priority = priorityName.toLowerCase();\n  if (priority.includes('critical')) criticalPriority++;\n  else if (priority.includes('high')) highPriority++;\n  else if (priority.includes('medium')) mediumPriority++;\n  else if (priority.includes('low')) lowPriority++;\n}\n\nconst portalUrl = 'https://api.samanage.com';\n\nconst insertQuery = `INSERT INTO solarwinds_summary (total_incidents, open_incidents, in_progress_incidents, resolved_incidents, critical_priority, high_priority, medium_priority, low_priority, portal_url, last_updated) VALUES (${totalIncidents}, ${openIncidents}, ${inProgressIncidents}, ${resolvedIncidents}, ${criticalPriority}, ${highPriority}, ${mediumPriority}, ${lowPriority}, ${escapeSql(portalUrl)}, ${escapeSql(timestamp)});`;\n\nconsole.log(`\\nSummary Statistics:`);\nconsole.log(`  Total: ${totalIncidents}`);\nconsole.log(`  Open: ${openIncidents}`);\nconsole.log(`  In Progress: ${inProgressIncidents}`);\nconsole.log(`  Resolved: ${resolvedIncidents}`);\nconsole.log(`  Priority - Critical: ${criticalPriority}, High: ${highPriority}, Medium: ${mediumPriority}, Low: ${lowPriority}`);\n\nreturn [{json: {query: insertQuery}}];"
          },
          "typeVersion": 2
        },
        {
          "id": "4db34a42-4799-4e35-8e79-fa579554e974",
          "name": "Insert Summary",
          "type": "n8n-nodes-base.microsoftSql",
          "position": [
            11344,
            1760
          ],
          "parameters": {
            "query": "={{ $json.query }}",
            "operation": "executeQuery"
          },
          "credentials": {
            "microsoftSql": {
              "id": "cQ8zhPt5LM1BWGFY",
              "name": "Microsoft SQL account 2"
            }
          },
          "typeVersion": 1.1
        },
        {
          "id": "0f99827d-9b12-4b92-9c8f-94ad0deb27a6",
          "name": "Webhook",
          "type": "n8n-nodes-base.webhook",
          "position": [
            9776,
            1568
          ],
          "webhookId": "53554e8e-62f4-4b23-9139-cf3a595f51a5",
          "parameters": {
            "path": "53554e8e-62f4-4b23-9139-cf3a595f51a5",
            "options": {}
          },
          "typeVersion": 2.1
        }
      ],
      "active": true,
      "pinData": {},
      "settings": {
        "callerPolicy": "workflowsFromSameOwner",
        "errorWorkflow": "2kE4fsL384DKsDZj",
        "timeSavedMode": "fixed",
        "availableInMCP": false,
        "executionOrder": "v1"
      },
      "versionId": "94eb8caa-4a26-4e24-ac0b-9ae4884814eb",
      "connections": {
        "Webhook": {
          "main": [
            [
              {
                "node": "Config - Base URL",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Manual Trigger": {
          "main": [
            [
              {
                "node": "Config - Base URL",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Truncate Tables": {
          "main": [
            [
              {
                "node": "Build Incidents Insert",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Insert Incidents": {
          "main": [
            [
              {
                "node": "Build Summary Insert",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Config - Base URL": {
          "main": [
            [
              {
                "node": "Get Incidents Page 1",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Schedule Every Hour": {
          "main": [
            [
              {
                "node": "Config - Base URL",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Build Summary Insert": {
          "main": [
            [
              {
                "node": "Insert Summary",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Get Incidents Page 1": {
          "main": [
            [
              {
                "node": "Truncate Tables",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Build Incidents Insert": {
          "main": [
            [
              {
                "node": "Insert Incidents",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      }
    }