# 通知設計書 93-new_review_email

## 概要

本ドキュメントは、GitLabにおける新規レビュー通知（new_review_email）の設計仕様を記述する。Merge Requestに新しいレビューが投稿された際に、関係するユーザーに対して送信されるメール通知の仕組みを定義する。

### 本通知の処理概要

**業務上の目的・背景**：コードレビューはソフトウェア開発プロセスにおいて品質担保の重要な役割を果たす。Merge Requestへのレビューが投稿されると、作成者や関係者は速やかにフィードバックを確認し対応する必要がある。この通知により、レビューの見落としを防ぎ、迅速なコードレビューサイクルを実現する。

**通知の送信タイミング**：レビューが投稿され、NotificationServiceのnew_reviewメソッドが呼び出されたタイミングで通知がトリガーされる。レビュー内のノートが内部ノート（internal note）のみの場合、受信者が内部ノートを閲覧できない場合は通知が送信されない。

**通知の受信者**：NotificationRecipients::BuildServiceのbuild_new_review_recipientsメソッドによって決定される。Merge Requestの作成者、アサイン先、レビュアー、メンションされたユーザーなどが対象となる。内部ノートの閲覧権限も考慮される。

**通知内容の概要**：レビュー投稿者の名前、Merge Requestの情報、レビュー内の各ノート（コメント）内容が含まれる。差分ディスカッションの場合は差分コンテキストも含まれる。

**期待されるアクション**：受信者はメールを確認後、Merge Requestページにアクセスしてレビューコメントに返信したり、指摘事項を修正したり、承認プロセスを進めることが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqのデフォルト設定に従う |

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

1. NotificationRecipients::BuildService.build_new_review_recipientsで受信者を構築
2. 受信者が内部ノートを閲覧できない場合、内部ノートを除外
3. 内部ノート除外後にノートが空の場合、通知をスキップ
4. 各受信者の`notification_email_for(merge_request.target_project.group)`を取得

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | レビュー投稿者のメールアドレス形式 |
| 送信元名称 | レビュー投稿者の名前 |
| 件名 | `Re: [{project_name}] {merge_request_title} ({merge_request_reference})` |
| 形式 | HTML/テキスト |

### 本文テンプレート

```
Merge request {mr_reference} was reviewed by {author_name}

--
[各ノートの内容]
- ノート1
--
- ノート2
...
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @review | レビューオブジェクト | Review.find(review_id) | Yes |
| @recipient | 受信者ユーザー | User.find(recipient_id) | Yes |
| @notes | レビュー内のノート一覧 | @review.notes | Yes |
| @discussions | ディスカッション | Discussion.build_discussions | Yes |
| @author | レビュー投稿者 | @review.author | Yes |
| @author_name | レビュー投稿者名 | @review.author_name | Yes |
| @project | プロジェクト | @review.project | Yes |
| @merge_request | Merge Request | @review.merge_request | Yes |
| @target_url | MRページURL | project_merge_request_url | Yes |
| @include_diff_discussion_stylesheet | 差分スタイル要否 | 差分ディスカッション有無 | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| サービス呼び出し | Review投稿 | ノートが存在、受信者存在 | NotificationService#new_review |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| @notes.blank? | 内部ノート除外後にノートがない場合 |
| 受信者リストが空 | 通知対象者がいない場合 |
| 内部ノートのみでread_internal_note権限なし | 受信者が内部ノートを閲覧できない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Review投稿] --> B[NotificationService#new_review]
    B --> C[build_new_review_recipients]
    C --> D{受信者存在?}
    D -->|No| E[終了]
    D -->|Yes| F[recipients.each]
    F --> G[new_review_email生成]
    G --> H{内部ノート権限チェック}
    H -->|権限なし| I[内部ノート除外]
    I --> J{ノート存在?}
    J -->|No| K[送信スキップ]
    J -->|Yes| L[mail_answer_thread]
    H -->|権限あり| L
    L --> M[deliver_later]
    M --> N[Sidekiqキューに追加]
    N --> O[非同期でメール送信]
    K --> F
    O --> F
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| reviews | レビュー情報の取得 | author_id, merge_request_id |
| notes | ノート（コメント）情報 | レビュー内のノート一覧 |
| merge_requests | Merge Request情報 | title, iid |
| users | ユーザー情報 | 投稿者、受信者 |
| projects | プロジェクト情報 | プロジェクト名 |
| sent_notifications | 送信通知記録 | 返信キー生成用 |

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

#### reviews

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| author_id | レビュー投稿者ID | 直接参照 |
| merge_request_id | MR ID | 直接参照 |
| project_id | プロジェクトID | 直接参照 |

#### notes

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| note | コメント内容 | @review.notes経由 |
| discussion_id | ディスカッションID | 直接参照 |
| internal | 内部ノートフラグ | 権限チェック用 |

### 更新テーブル一覧

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

#### sent_notifications

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | noteable_id | merge_request.id | 返信追跡用 |
| INSERT | recipient_id | recipient.id | 受信者ID |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPエラー | Sidekiqによるリトライ |
| レビュー不存在 | Review.find_by_id(review_id).nil? | 処理スキップ |
| 受信者不存在 | User.find(recipient_id)失敗 | 例外発生 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Notifyクラスのcheck_rate_limitに従う |
| 1日あたり上限 | Notifyクラスのcheck_rate_limitに従う |

### 配信時間帯

制限なし（リアルタイム送信）

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

- 内部ノート（internal note）は権限のないユーザーには送信されない
- 差分内容はプロジェクトへのアクセス権を持つユーザーにのみ表示
- SentNotificationによる返信キーでメール返信の認証を実現

## 備考

- mail_answer_threadを使用してスレッド形式でメールを送信
- 差分ディスカッションがある場合はスタイルシートが追加される
- deliver_optionsでカスタム配信オプションを設定可能

---

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

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

### 推奨読解順序

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

ReviewモデルとNote、MergeRequestの関連を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | review.rb | `app/models/review.rb` | author, merge_request, notesの関連を確認 |

**読解のコツ**: ReviewはMergeRequestに属し、複数のNotesを持つ。discussion_idsメソッドでディスカッションIDを取得する。

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

NotificationServiceでの呼び出し処理を確認。

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

**主要処理フロー**:
1. **698行目**: build_new_review_recipientsで受信者構築
2. **699行目**: deliver_optionsの取得
3. **701-705行目**: 各受信者へnew_review_email送信

#### Step 3: メーラークラスを理解する

通知メールの構築ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | reviews.rb | `app/mailers/emails/reviews.rb` | new_review_emailメソッド、setup_review_email |

**主要処理フロー**:
- **5-17行目**: new_review_emailメソッド - 内部ノートのフィルタリングとメール送信
- **10-13行目**: 内部ノートの権限チェックと除外
- **29-43行目**: setup_review_emailメソッド - インスタンス変数設定

#### Step 4: ビューテンプレートを理解する

メール本文の構造を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | new_review_email.html.haml | `app/views/notify/new_review_email.html.haml` | HTML形式のテンプレート構造 |
| 4-2 | new_review_email.text.erb | `app/views/notify/new_review_email.text.erb` | テキスト形式のテンプレート |

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

```
NotificationService#new_review (app/services/notification_service.rb:697)
    │
    ├─ NotificationRecipients::BuildService.build_new_review_recipients
    │      └─ 受信者リスト構築
    │
    └─ recipients.each
           │
           └─ mailer.new_review_email(recipient.user.id, review.id)
                  │
                  ├─ Emails::Reviews#new_review_email
                  │      ├─ setup_review_email (インスタンス変数設定)
                  │      ├─ 内部ノート権限チェック
                  │      │      ├─ @recipient.can?(:read_internal_note, @project)
                  │      │      └─ @notes = @notes.reject(&:internal?)
                  │      └─ mail_answer_thread (スレッドメール送信)
                  │
                  └─ deliver_later (Sidekiq)
```

### データフロー図

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

Review投稿            ───▶ NotificationService         ───▶ Notify.new_review_email
(author, notes)            (new_review)                     │
                                                           ▼
@review.notes         ───▶ 内部ノートフィルタ          ───▶ @notes
                           (権限チェック)                   │
                                                           ▼
Discussion.build      ───▶ ディスカッション構築        ───▶ @discussions
(discussion_ids)           (差分情報含む)                   │
                                                           ▼
note_emailパーシャル  ───▶ ノート本文レンダリング      ───▶ メール本文
                           (差分コンテキスト含む)           │
                                                           ▼
mail_answer_thread    ───▶ Sidekiqキュー              ───▶ 受信者へメール送信
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| review.rb | `app/models/review.rb` | ソース | レビューモデル |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス |
| reviews.rb | `app/mailers/emails/reviews.rb` | ソース | レビューメーラーモジュール |
| notify.rb | `app/mailers/notify.rb` | ソース | メインメーラークラス |
| new_review_email.html.haml | `app/views/notify/new_review_email.html.haml` | テンプレート | HTML形式メール本文 |
| new_review_email.text.erb | `app/views/notify/new_review_email.text.erb` | テンプレート | テキスト形式メール本文 |
| _note_email.html.haml | `app/views/notify/_note_email.html.haml` | テンプレート | ノートパーシャル（HTML） |
| _note_email.text.erb | `app/views/notify/_note_email.text.erb` | テンプレート | ノートパーシャル（テキスト） |
