# 通知設計書 25-request_review_merge_request_email

## 概要

本ドキュメントは、Merge Requestのレビューがリクエストされた際に、レビュアーへ通知するメール送信機能の設計を記載する。

### 本通知の処理概要

Merge Requestのレビューがリクエストされたことを、指定されたレビュアーにメールで通知する機能である。

**業務上の目的・背景**：コードレビューはソフトウェア開発において品質保証の重要なプロセスである。レビュアーとして指定されたユーザーは、レビューリクエストを認識し、適切なタイミングでレビューを開始する必要がある。本通知により、レビュアーはレビュー依頼を即座に把握し、コードレビューの迅速な開始が可能となる。

**通知の送信タイミング**：Merge Requestにレビュアーが追加された、または既存のレビュアーにレビューが再リクエストされた時点で送信される。具体的には、NotificationServiceのreview_requested_of_merge_requestメソッドが呼び出された際にトリガーされる。

**通知の受信者**：レビューをリクエストされた特定のレビュアーのみが受信対象となる。RequestReviewビルダーを使用し、指定されたレビュアーに対してのみ通知を送信する。

**通知内容の概要**：レビューがリクエストされたこと、リクエストした人の名前、対象Merge RequestのURL、Merge Requestのタイトルと参照番号が含まれる。EE版では差分サマリーも含まれる場合がある。

**期待されるアクション**：受信者（レビュアー）は、Merge Requestにアクセスしてコードの変更内容を確認し、コードレビューを実施して承認またはコメントを行うことが期待される。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqの標準リトライ設定に準拠 |

### 送信先決定ロジック

NotificationRecipients::BuildService.build_requested_review_recipientsを使用して受信者を決定する。以下の特徴がある：

1. RequestReviewビルダーが使用される
2. 指定されたレビュアー（reviewer）のみが対象
3. 通知理由としてNotificationReason::REVIEW_REQUESTEDが設定される
4. 通知レベル:mentionで追加される

EE版では、review_request_deliver_optionsを通じて配信オプションがカスタマイズされる場合がある。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabの設定に基づくシステムメールアドレス |
| 送信元名称 | 操作実行者の名前（user_name (user_reference)形式） |
| 件名 | Re: {project_name} \| {merge_request_title} ({merge_request_reference}) |
| 形式 | HTML/テキスト両対応 |

### 本文テンプレート

**テキスト形式**:
```
{updated_by_name} requested a new review on {merge_request_url}
{diff_summary} (EE版のみ)
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 本通知に添付ファイルはない |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @merge_request | 対象のMerge Request | MergeRequest.find(merge_request_id) | Yes |
| @project | 対象プロジェクト | @merge_request.project | Yes |
| @target_url | Merge RequestのURL | project_merge_request_url | Yes |
| @recipient | 受信者（レビュアー） | User.find(recipient_id) | Yes |
| @updated_by | レビューをリクエストした人 | User.find(updated_by_user_id) | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | レビュアー追加 | MRにレビュアーが追加された場合 | UIからのレビュアー追加操作 |
| 画面操作 | レビュー再リクエスト | 既存レビュアーに再レビューをリクエスト | レビュー再リクエストボタン押下 |
| API | MergeRequest更新API | reviewer_idsが更新された場合 | REST/GraphQL API経由でのレビュアー追加 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| プロジェクトのメール通知無効 | project.emails_disabled?がtrueの場合 |
| ユーザーの通知設定 | 受信者の通知レベルがDisabledの場合 |
| レビュアーがMRを閲覧不可 | レビュアーがMerge Requestの閲覧権限を持たない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[レビューリクエストイベント] --> B[NotificationService.review_requested_of_merge_request]
    B --> C[NotificationRecipients::BuildService.build_requested_review_recipients]
    C --> D[RequestReviewビルダー]
    D --> E[add_recipients with :mention, REVIEW_REQUESTED]
    E --> F{受信者が存在?}
    F -->|Yes| G[各受信者へメール送信]
    F -->|No| H[終了]
    G --> I[Notify.request_review_merge_request_email]
    I --> J[setup_merge_request_mail]
    J --> K[@updated_by セット]
    K --> L[mail_answer_thread]
    L --> M[deliver_later with deliver_option]
    M --> N[Sidekiqキュー登録]
    N --> O[終了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| merge_requests | 対象MR情報取得 | merge_request_id指定 |
| users | レビュアー・操作者情報 | reviewer_id, updated_by_user_id |
| projects | プロジェクト情報 | merge_request.project |
| notification_settings | 通知設定確認 | 受信者決定時 |
| merge_request_reviewers | レビュアー関連 | reviewer情報 |

### テーブル別参照項目詳細

#### merge_requests

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | MR識別 | WHERE id = merge_request_id |
| title | メール件名 | - |
| iid | MR参照番号 | to_reference用 |
| project_id | プロジェクト取得 | - |
| target_project_id | ターゲットプロジェクト | URL生成用 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| sent_notifications | INSERT | 送信通知の記録 |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | noteable_type | 'MergeRequest' | 対象種別 |
| INSERT | noteable_id | merge_request_id | 対象ID |
| INSERT | recipient_id | recipient_id | 受信者ID |
| INSERT | reply_key | 生成されたキー | 返信用キー |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPエラー | Sidekiqによる自動リトライ |
| MR未存在 | 削除済みMR | ActiveRecord::RecordNotFoundで処理中断 |
| ユーザー未存在 | 削除済みユーザー | 該当ユーザーをスキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なネットワークエラー、SMTPタイムアウト |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Gitlab::ApplicationRateLimiter設定に準拠 |
| 1日あたり上限 | 設定なし |

### 配信時間帯

制限なし（イベント発生時に即座に配信）

## セキュリティ考慮事項

- 受信者はMerge Requestの閲覧権限を持つユーザーに限定される
- ユーザー名はsanitize_nameでサニタイズされて表示される
- Reply-Toにはpartitioned_reply_keyを使用し、返信の認証に利用

## 備考

- RequestReviewビルダーは、指定されたレビュアーのみを対象とする特化型のビルダー
- EE版ではreview_request_deliver_optionsで配信オプションがカスタマイズされる
- EE版のテンプレートでは、render_if_existsを使用して差分サマリー（diff_summary）が追加される場合がある
- 通知理由（NotificationReason::REVIEW_REQUESTED）により、メールヘッダーにX-GitLab-NotificationReasonが設定される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、通知の対象となるMerge Requestとレビュアーの関係を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | merge_request.rb | `app/models/merge_request.rb` | MergeRequestモデルのreviewers関連 |
| 1-2 | merge_request_reviewer.rb | `app/models/merge_request_reviewer.rb` | レビュアー中間テーブル |

**読解のコツ**: MergeRequestとUserはmerge_request_reviewersテーブルを介して多対多の関係。

#### Step 2: エントリーポイントを理解する

処理の起点となるNotificationServiceを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_service.rb | `app/services/notification_service.rb` | review_requested_of_merge_requestメソッド（403-413行目） |

**主要処理フロー**:
1. **403行目**: review_requested_of_merge_requestメソッドの定義開始
2. **404行目**: build_requested_review_recipientsで受信者を決定
3. **406行目**: review_request_deliver_optionsで配信オプション取得（EEでオーバーライド）
4. **408-412行目**: 各受信者へrequest_review_merge_request_emailを送信

#### Step 3: 受信者決定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | build_service.rb | `app/services/notification_recipients/build_service.rb` | build_requested_review_recipientsメソッド（36-38行目） |
| 3-2 | request_review.rb | `app/services/notification_recipients/builder/request_review.rb` | RequestReviewビルダーの実装 |

**主要処理フロー**:
- **request_review.rb 18-19行目**: build!メソッドで指定されたレビュアーのみを:mentionレベルで追加
- **request_review.rb 19行目**: 通知理由としてREVIEW_REQUESTEDを設定

#### Step 4: メール送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | merge_requests.rb | `app/mailers/emails/merge_requests.rb` | request_review_merge_request_emailメソッド（140-145行目） |
| 4-2 | notify.rb | `app/mailers/notify.rb` | 基底クラスとメール送信共通処理 |

**主要処理フロー**:
- **140-145行目**: setup_merge_request_mailでMR情報をセットアップ
- **143行目**: @updated_byにレビューをリクエストした人をセット
- **144行目**: mail_answer_threadでメール送信

#### Step 5: テンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | request_review_merge_request_email.text.erb | `app/views/notify/request_review_merge_request_email.text.erb` | テキストメール本文 |

**読解のコツ**: render_if_existsを使用してEE版のdiff_summaryパーシャルが存在する場合のみ追加レンダリングされる。

### プログラム呼び出し階層図

```
NotificationService.review_requested_of_merge_request(merge_request, current_user, reviewer)
    │
    ├─ NotificationRecipients::BuildService.build_requested_review_recipients
    │      └─ Builder::RequestReview.new(merge_request, current_user, reviewer)
    │             └─ add_recipients(reviewer, :mention, REVIEW_REQUESTED)
    │
    ├─ review_request_deliver_options(merge_request.project)
    │      └─ {} (EEでオーバーライド)
    │
    └─ Notify.request_review_merge_request_email
           ├─ setup_merge_request_mail
           │      ├─ MergeRequest.find
           │      ├─ User.find(recipient_id)
           │      └─ SentNotification.record
           ├─ @updated_by = User.find(updated_by_user_id)
           └─ mail_answer_thread
                  └─ deliver_later(deliver_option)
```

### データフロー図

```
[入力]                    [処理]                           [出力]

MergeRequest ───────────▶ NotificationService
                         .review_requested_of_merge_request
current_user ───────────▶     │
                              │
reviewer ───────────────▶     │
                              ▼
                         BuildService.build_requested_review_recipients
                              │
                              ▼
                         RequestReview.build! ───────────▶ recipients[reviewer]
                              │
                              ▼
                         Notify.request_review_merge_request_email
                              │
                              ├─ @updated_by
                              ▼
                         mail_answer_thread ─────────────▶ Email送信
                              │
                              ▼
                         SentNotification.record ────────▶ sent_notifications
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービスのエントリーポイント |
| build_service.rb | `app/services/notification_recipients/build_service.rb` | ソース | 受信者決定サービス |
| request_review.rb | `app/services/notification_recipients/builder/request_review.rb` | ソース | レビューリクエスト専用ビルダー |
| base.rb | `app/services/notification_recipients/builder/base.rb` | ソース | ビルダー基底クラス |
| merge_requests.rb | `app/mailers/emails/merge_requests.rb` | ソース | MR関連メールメソッド定義 |
| notify.rb | `app/mailers/notify.rb` | ソース | メーラー基底クラス |
| request_review_merge_request_email.text.erb | `app/views/notify/request_review_merge_request_email.text.erb` | テンプレート | テキストメール本文 |
| notification_reason.rb | `app/models/notification_reason.rb` | ソース | 通知理由定数（REVIEW_REQUESTED） |
