iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Software Development

AI 驅動的 Code Review:MCP 與 n8n 自動化實踐系列 第 20

[Day20] 團隊使用 n8n + AI + API 做 Code Review 的最終方案:Discussion API 實戰 (上)

  • 分享至 

  • xImage
  •  

團隊使用 n8n + AI + API 做 Code Review 的最終方案:Discussion API 實戰 (上)

前言

經過前面章節對 Note API 方式的探討,我們了解到那種方法更適合作為 POC(概念驗證)階段使用。今天我們要深入探討團隊真正導入 production 環境的完整解決方案 —— 基於 Discussion API 的精確行級 Code Review 系統。

這個方案的核心優勢在於能夠將 AI 的審查建議精確地標註到程式碼的特定行數上,而不是僅僅在 Merge Request 中留下總體評論,大幅提升了 Code Review 的實用性和可操作性。

最終方案架構圖

完整流程架構

詳細流程圖

詳細實作步驟

步驟 1:多專案管理架構 (webhook 節點)

預計於 Day 23 篇章詳細說明多專案權限管理,這邊只需要知道把專案的 webhook 帶入到對應專案裡面。

https://ithelp.ithome.com.tw/upload/images/20250923/20121499i20Cu8mtqy.png

步驟 2:條件判斷 (if 節點)

https://ithelp.ithome.com.tw/upload/images/20250923/201214990YySFLpY9S.png

觸發條件設計

帶入以下程式碼,
為了避免不必要的資源消耗和 API 調用,
我們需要精確控制什麼情況下才觸發 AI Code Review。

{{ 
  (
    $json.body.object_attributes.action === "open" ||
    $json.body.object_attributes.action === "update" 
  ) && 
  $json.body.object_attributes.state === "opened" 
}}

條件判斷節點設定

邏輯說明:

  • 動作條件:只有當 MR 被新建(open)或更新(update)時才觸發
  • 狀態條件:只有處於開啟狀態(opened)的 MR 才需要審查
  • 效益:避免對已合併或關閉的 MR 進行無意義的審查

步驟 3:多專案權限控管系統 (程式節點)

https://ithelp.ithome.com.tw/upload/images/20250923/20121499t7pJkV0z7c.png

安全性考量

不同專案需要使用各自的 GitLab Token 來確保權限隔離和安全性。
延續步驟 1,Day 23 會詳細說明為什麼要這樣寫。

// 專案 token 設定: 格式 key:id, value:token
var mapToken = new Map();
mapToken.set("4835", "token callduck") // callduck 專案
mapToken.set("5036", "token beacon")   // beacon 專案

return $input.all().map(item => {
  const projectId = item.json.body.project.id;
  const iid = item.json.body.object_attributes.iid;
  const token = mapToken.get(projectId.toString());
  const sourceBranch = item.json.body.object_attributes.source_branch;
  
  return {
    json: {
      projectId: projectId,
      iid: iid,
      token: token,
      sourceBranch: sourceBranch
    }
  }
});

步驟 4:GitLab API 資料獲取 (HTTP Request 節點)

https://ithelp.ithome.com.tw/upload/images/20250923/20121499eBr69ruxXW.png

API 調用設定

這個步驟展示了如何將前一步驟獲取的 Token 動態注入到 GitLab API 調用中

步驟 5:Discussion API 精確定位

https://ithelp.ithome.com.tw/upload/images/20250923/20121499UO4O97kMZe.png

核心邏輯

  • 每份檔案都留言在第一個異動行數留言
  • create discussion API
  • 參數: 異動行數、hash 碼、異動檔案

技術實作

const changes = $json.changes || [];
const headSha = $json?.diff_refs?.head_sha || '';
const baseSha = $json?.diff_refs?.base_sha || '';
const startSha = $json?.diff_refs?.start_sha || baseSha;

const projectId = $json.project_id || 0;
const iid = $json.iid || 0;

// 解析每個 diff 的第一個異動行
function getFirstChangedLine(diff) {
  const lines = diff.split('\n');
  let oldLineNum = 0;
  let newLineNum = 0;
  let headerFound = false;

  for (const line of lines) {
    if (line.startsWith('@@')) {
      // 解析 diff header,獲取行數資訊
      const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
      if (match) {
        oldLineNum = parseInt(match[1]) - 1;
        newLineNum = parseInt(match[2]) - 1;
        headerFound = true;
      }
    } else if (headerFound && line.startsWith('+') && !line.startsWith('+++')) {
      // 處理新增的行
      newLineNum++;
      return {
        type: 'added',
        old_line: null,
        new_line: newLineNum,
        content: line.substring(1),
        original_line: line
      };
    } else if (headerFound && line.startsWith('-') && !line.startsWith('---')) {
      // 處理刪除的行
      oldLineNum++;
      return {
        type: 'removed',
        old_line: oldLineNum,
        new_line: null,
        content: line.substring(1),
        original_line: line
      };
    } else if (headerFound && line.startsWith(' ')) {
      // 處理未改變的行
      oldLineNum++;
      newLineNum++;
    }
  }

  return null;
}

// 為每個檔案生成第一個變更行資料
const changesWithFirstLine = changes.map(change => {
  const firstChangedLine = getFirstChangedLine(change.diff);
  
  if (!firstChangedLine) {
    return null;
  }

  return {
    diff: change.diff,
    new_path: change.new_path,
    old_path: change.old_path || change.new_path, // 確保 old_path 不為空
    new_file: change.new_file,
    deleted_file: change.deleted_file,
    old_line: firstChangedLine.old_line,
    new_line: firstChangedLine.new_line,
    baseSha: baseSha,
    headSha: headSha,
    startSha: startSha
  };
}).filter(item => item !== null); // 過濾掉無效項目

// 輸出符合 GitLab Discussion API 格式的資料
return [
  {
    json: {
      project_id: projectId,
      iid,
      changes_with_first_line: changesWithFirstLine
    }
  }
];

程式碼詳解

Diff 解析邏輯:

  1. Header 識別:找到以 @@ 開頭的 diff header,解析出起始行號
  2. 行數追蹤:分別追蹤舊檔案和新檔案的行號變化
  3. 變更定位:識別第一個發生變更的行(新增或刪除)
  4. 座標計算:計算出該行在 GitLab 中的精確座標

資料結構設計:

  • 完整 Diff:保留完整的 diff 內容供 AI 分析
  • 定位資訊:提供 Discussion API 需要的行號和 SHA 值
  • 檔案資訊:包含檔案路徑和檔案狀態(新建/刪除)

小結

這個 Discussion API 代表了我們團隊在 Code Review 自動化的使用方案。相比於前面介紹的 Note API 方案,這個版本在實用性、精確性和用戶體驗方面都有顯著提升。

由於這個方案涉及較多的程式碼實作細節,分成上中下篇章


上一篇
[Day19] n8n Code Review 自動化: 使用 API 取代 MCP Tool 的完整解決方案
下一篇
[Day 21] 團隊使用 n8n + AI + API 做 Code Review 的最終方案:Discussion API 實戰 (中)
系列文
AI 驅動的 Code Review:MCP 與 n8n 自動化實踐22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言