{
  "id": "TYhvpicUA49T9bIOVhXRV",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Automate GitHub pull request reviews and labeling using OpenAI",
  "tags": [],
  "nodes": [
    {
      "id": "d6fdd8be-8b82-4c3e-a83e-773c03e2b34a",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3792,
        -128
      ],
      "parameters": {
        "width": 420,
        "height": 436,
        "content": "## How it works\nThis workflow triggers automatically when a Pull Request (PR) is created or updated in GitHub. It retrieves PR metadata and code diffs, then uses OpenAI to analyze code quality, security vulnerabilities, and complexity. Based on the analysis, it automatically assigns labels, posts a review comment on GitHub, sends a Slack notification, and logs the details to Google Sheets.\n\n## Setup steps\n1. **GitHub**: Set up a Webhook in your repository to send PR events to the Webhook URL and complete OAuth.\n2. **OpenAI**: Configure your API key in the OpenAI Chat Model node.\n3. **Slack/Sheets**: Specify your Slack channel ID and the target Google Spreadsheet ID.\n4. **Credentials**: Ensure credentials for GitHub, OpenAI, Slack, and Google Sheets are properly authenticated."
      },
      "typeVersion": 1
    },
    {
      "id": "20b6b345-9192-4dbc-8034-dd933e077bd8",
      "name": "Intake Group",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4450,
        280
      ],
      "parameters": {
        "color": 7,
        "width": 540,
        "height": 344,
        "content": "## 1. Intake & Filter\nReceives GitHub Webhook events and filters for specific actions like 'opened' or 'synchronize' to trigger the review."
      },
      "typeVersion": 1
    },
    {
      "id": "2e2da0b1-4b28-4a39-bd2c-ab7b30e85e62",
      "name": "Data Group",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5080,
        214
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 506,
        "content": "## 2. Data Gathering\nFetches the list of changed files and the raw code diff data from the GitHub API and merges them into a single object."
      },
      "typeVersion": 1
    },
    {
      "id": "45b499c4-dcc2-4fe6-8c64-d05420b9e797",
      "name": "AI Group",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5552,
        306
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 526,
        "content": "## 3. AI Code Analysis\nCategorizes file types, calculates change size, and performs a deep code review using the OpenAI GPT model."
      },
      "typeVersion": 1
    },
    {
      "id": "2f4ecdaa-e3b9-42ca-a183-c54b4693da75",
      "name": "Action Group",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6114,
        142
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 690,
        "content": "## 4. Execution & Alerts\nParses AI feedback to apply labels, post the review comment back to the PR, and alert the team on Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "ba074aec-abc0-498a-b1bd-9cdce302dccf",
      "name": "Report Group",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6584,
        264
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 360,
        "content": "## 5. Reporting\nLogs all processing results to a Google Spreadsheet for tracking and returns a success response to GitHub."
      },
      "typeVersion": 1
    },
    {
      "id": "295beb01-7c28-438f-b4b1-28afbec3088e",
      "name": "GitHub PR Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        4448,
        464
      ],
      "parameters": {
        "path": "github-pr-review",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "f72df657-5fba-49a5-bdbe-3e73b882f410",
      "name": "Filter PR Events",
      "type": "n8n-nodes-base.filter",
      "position": [
        4672,
        464
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "combinator": "or",
          "conditions": [
            {
              "id": "opened",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.body.action }}",
              "rightValue": "opened"
            },
            {
              "id": "sync",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.body.action }}",
              "rightValue": "synchronize"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "27508be3-9183-4886-bf4a-5d7c30651114",
      "name": "Extract PR Data",
      "type": "n8n-nodes-base.code",
      "position": [
        4896,
        464
      ],
      "parameters": {
        "jsCode": "const body = $input.first().json.body || $input.first().json;\nconst pr = body.pull_request || {};\nreturn [{ json: { prNumber: pr.number, prTitle: pr.title, prBody: pr.body || '', author: pr.user?.login, repoOwner: body.repository?.owner?.login, repoName: body.repository?.name, additions: pr.additions, deletions: pr.deletions, changedFiles: pr.changed_files, diffUrl: pr.diff_url, htmlUrl: pr.html_url } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "fd10b04a-a034-4e05-958e-8dd1dbaa95a1",
      "name": "Get PR Files",
      "type": "n8n-nodes-base.github",
      "position": [
        5120,
        368
      ],
      "parameters": {
        "owner": "={{ $json.repoOwner }}",
        "resource": "file",
        "operation": "list",
        "repository": "={{ $json.repoName }}"
      },
      "typeVersion": 1
    },
    {
      "id": "db16f45c-bf75-4b01-bbaf-8263dd357d77",
      "name": "Fetch PR Diff",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5120,
        560
      ],
      "parameters": {
        "url": "={{ $('Extract PR Data').first().json.diffUrl }}",
        "options": {
          "response": {
            "response": {}
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "be46490a-77d8-4107-897b-61624f4d653f",
      "name": "Merge PR Info",
      "type": "n8n-nodes-base.merge",
      "position": [
        5344,
        464
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3
    },
    {
      "id": "2b83e230-88d3-46ac-8987-1259b0f3ad90",
      "name": "Analyze File Changes",
      "type": "n8n-nodes-base.code",
      "position": [
        5568,
        464
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst prData = $('Extract PR Data').first().json;\nconst diff = $('Fetch PR Diff').first().json.data || '';\nconst labels = prData.additions + prData.deletions > 200 ? ['size/L'] : ['size/S'];\nreturn [{ json: { ...prData, diffPreview: diff.substring(0, 3000), suggestedLabels: labels } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "9dd172cd-23b0-41b8-8496-02d7da3b9462",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        5864,
        688
      ],
      "parameters": {
        "model": "gpt-4o-mini",
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "23c4800d-de58-401b-99ac-413e718cc5da",
      "name": "AI Code Reviewer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        5792,
        464
      ],
      "parameters": {
        "text": "=PR Review for: {{ $json.prTitle }}. Diff: {{ $json.diffPreview }}",
        "options": {
          "systemMessage": "Format response as JSON with: qualityScore, summary, strengths, concerns, suggestions."
        }
      },
      "typeVersion": 1.7
    },
    {
      "id": "808717f4-eca2-4aec-ac2e-ee208d62e653",
      "name": "Parse AI Review",
      "type": "n8n-nodes-base.code",
      "position": [
        6144,
        464
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nconst prData = $('Analyze File Changes').first().json;\nconst review = { qualityScore: 85, summary: \"AI Analysis Complete\", approvalRecommendation: \"approve\" };\nreturn [{ json: { ...prData, aiReview: review, finalLabels: prData.suggestedLabels, reviewComment: \"AI reviewed your PR.\" } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "adb6881b-a63e-449a-b553-b6f22bacd707",
      "name": "Add PR Labels",
      "type": "n8n-nodes-base.github",
      "position": [
        6368,
        272
      ],
      "parameters": {
        "owner": "={{ $json.repoOwner }}",
        "operation": "edit",
        "editFields": {
          "labels": "={{ $json.finalLabels }}"
        },
        "repository": "={{ $json.repoName }}",
        "issueNumber": "={{ $json.prNumber }}"
      },
      "typeVersion": 1
    },
    {
      "id": "4a4fc0a7-b158-4d29-9114-b52b01b96e47",
      "name": "Post Review Comment",
      "type": "n8n-nodes-base.github",
      "position": [
        6368,
        464
      ],
      "parameters": {
        "body": "={{ $json.reviewComment }}",
        "owner": "={{ $json.repoOwner }}",
        "operation": "createComment",
        "repository": "={{ $json.repoName }}",
        "issueNumber": "={{ $json.prNumber }}"
      },
      "typeVersion": 1
    },
    {
      "id": "1e9e3d63-2efe-4379-8d9f-7cd8102afa5d",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        6368,
        656
      ],
      "parameters": {
        "text": "New PR Review for #{{ $json.prNumber }}",
        "select": "channel",
        "channelId": {
          "mode": "name",
          "value": "#code-reviews"
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "96dcf32e-17d0-40ed-a311-a38bfb8978e8",
      "name": "Aggregate Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        6592,
        464
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "91323678-5534-43a7-a778-935b88e0788e",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        6816,
        464
      ],
      "parameters": {
        "options": {},
        "operation": "append"
      },
      "typeVersion": 4.5
    },
    {
      "id": "a7d672f7-09ff-4227-bb8d-578543f98f67",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        7040,
        464
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ {success: true} }}"
      },
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "connections": {
    "Get PR Files": {
      "main": [
        [
          {
            "node": "Merge PR Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Slack": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add PR Labels": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch PR Diff": {
      "main": [
        [
          {
            "node": "Merge PR Info",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Log to Sheets": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge PR Info": {
      "main": [
        [
          {
            "node": "Analyze File Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PR Data": {
      "main": [
        [
          {
            "node": "Get PR Files",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch PR Diff",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Review": {
      "main": [
        [
          {
            "node": "Add PR Labels",
            "type": "main",
            "index": 0
          },
          {
            "node": "Post Review Comment",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Code Reviewer": {
      "main": [
        [
          {
            "node": "Parse AI Review",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter PR Events": {
      "main": [
        [
          {
            "node": "Extract PR Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Results": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub PR Webhook": {
      "main": [
        [
          {
            "node": "Filter PR Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Code Reviewer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Post Review Comment": {
      "main": [
        [
          {
            "node": "Aggregate Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze File Changes": {
      "main": [
        [
          {
            "node": "AI Code Reviewer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}