# 通知設計書 27-merge_request_unmergeable_email

## 概要

本ドキュメントは、Merge Requestがマージ不可になった際に、関係者へ通知するメール送信機能の設計を記載する。

### 本通知の処理概要

Merge Requestがコンフリクト等によりマージ不可状態になったことを、関係者にメールで通知する機能である。

**業務上の目的・背景**：Merge Requestがマージ可能状態からマージ不可状態に変化した場合、コンフリクトの解消など追加の作業が必要となる。本通知により、Merge Requestの作成者やマージ担当者が即座にマージ不可状態を認識し、コンフリクト解消のための対応を取ることができる。

**通知の送信タイミング**：Merge Requestがマージ不可状態に変化した時点で送信される。具体的には、NotificationServiceのmerge_request_unmergeableメソッドが呼び出された際にトリガーされる。

**通知の受信者**：Merge Requestの「マージ参加者」（merge_participants）が受信対象となる。具体的には、作成者（author）と、自動マージが設定されている場合はマージ担当者（merge_user）も含まれる。MergeRequestUnmergeableビルダーが使用される特殊な受信者決定ロジックとなっている。

**通知内容の概要**：Merge Requestがコンフリクトによりマージできなくなったこと、対象Merge RequestのURL、Merge Requestのタイトル、参照番号、作成者名、アサイン先、レビュアーが含まれる。

**期待されるアクション**：受信者は、コンフリクトの内容を確認し、リベースまたはマージによりコンフリクトを解消することが期待される。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 高（マージをブロックする問題のため） |
| リトライ | Sidekiqの標準リトライ設定に準拠 |

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

NotificationRecipients::BuildService.build_merge_request_unmergeable_recipientsを使用して受信者を決定する。MergeRequestUnmergeableビルダーが使用され、以下の特徴がある：

1. merge_participantsメソッドで取得されるユーザーが対象
2. 作成者（author）は必ず含まれる
3. 自動マージ設定時（auto_merge_enabled?）はマージ担当者（merge_user）も含まれる
4. MRの閲覧権限を持つユーザーのみにフィルタリングされる

acting_userはnilとなり、操作者の除外は行われない。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

**テキスト形式**:
```
Merge request {merge_request_reference} can no longer be merged due to conflict.

Merge request URL: {merge_request_url}

{source_branch} -> {target_branch}

Author: {author_name}
Assignees: {assignees}
Reviewers: {reviewers}
```

**HTML形式**:
```html
<p>Merge request {merge_request_link} can no longer be merged due to conflict.</p>
<p>{source_branch} -> {target_branch}</p>
<p>Author: {author_name}</p>
<p>Assignees: {assignees}</p>
<p>Reviewers: {reviewers}</p>
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @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 |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 自動処理 | ターゲットブランチへの変更 | MRがマージ可能状態からマージ不可に変化した場合 | 他のMRマージやコミットによるコンフリクト発生 |
| 自動処理 | ソースブランチの変更 | コンフリクトが発生した場合 | ソースブランチ更新によるコンフリクト |
| 自動処理 | Merge Train処理 | マージトレイン内でコンフリクトが検出された場合 | Enterprise機能 |

### 送信抑止条件

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

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[マージ不可検出] --> B[NotificationService.merge_request_unmergeable]
    B --> C[merge_request_unmergeable_email]
    C --> D[NotificationRecipients::BuildService.build_merge_request_unmergeable_recipients]
    D --> E[MergeRequestUnmergeableビルダー]
    E --> F[merge_participants取得]
    F --> G{受信者が存在?}
    G -->|Yes| H[各受信者へメール送信]
    G -->|No| I[終了]
    H --> J[Notify.merge_request_unmergeable_email]
    J --> K[setup_merge_request_mail]
    K --> L[mail_answer_thread]
    L --> M[deliver_later]
    M --> N[Sidekiqキュー登録]
    N --> O[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| merge_requests | 対象MR情報取得 | merge_request_id指定 |
| users | 受信者・作成者情報 | author_id, merge_user_id |
| projects | プロジェクト情報 | merge_request.project |
| notification_settings | 通知設定確認 | 受信者決定時 |

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

#### merge_requests

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | MR識別 | WHERE id = merge_request_id |
| title | メール件名 | - |
| iid | MR参照番号 | to_reference用 |
| project_id | プロジェクト取得 | - |
| author_id | 作成者情報（merge_participants） | - |
| merge_user_id | マージ担当者 | auto_merge_enabled時 |
| auto_merge_enabled | 自動マージ設定 | merge_participants判定用 |

### 更新テーブル一覧

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

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

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

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

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

## 備考

- MergeRequestUnmergeableビルダーは、マージ参加者のみを対象とする特化型のビルダー
- acting_userがnilのため、誰も除外されずに通知される
- custom_actionは:unmergeable_merge_requestとして設定される
- merge_participantsは作成者と（自動マージ時の）マージ担当者を返すメソッド

---

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

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

### 推奨読解順序

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

まず、通知の対象となるMerge Requestのマージ参加者の定義を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | merge_request.rb | `app/models/merge_request.rb` | merge_participantsメソッド（1032-1040行目） |

**読解のコツ**: merge_participantsは作成者（author）を必ず含み、auto_merge_enabled?の場合はmerge_userも含める。閲覧権限チェックも行う。

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

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

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

**主要処理フロー**:
1. **332-334行目**: merge_request_unmergeableメソッドがmerge_request_unmergeable_emailを呼び出す

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | merge_request_unmergeable_emailメソッド（870-876行目） |
| 3-2 | build_service.rb | `app/services/notification_recipients/build_service.rb` | build_merge_request_unmergeable_recipientsメソッド（24-26行目） |
| 3-3 | merge_request_unmergeable.rb | `app/services/notification_recipients/builder/merge_request_unmergeable.rb` | 専用ビルダー |

**主要処理フロー**:
- **merge_request_unmergeable.rb 12-15行目**: build!メソッドでmerge_participantsを:participatingレベルで追加
- **merge_request_unmergeable.rb 22-24行目**: acting_userがnilを返す（誰も除外しない）

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

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

**主要処理フロー**:
- **155-159行目**: setup_merge_request_mailでMR情報をセットアップ
- **158行目**: mail_answer_threadで送信（送信者はMR作成者のauthor_id）

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

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

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

```
NotificationService.merge_request_unmergeable(merge_request)
    │
    └─ merge_request_unmergeable_email(merge_request)
           │
           ├─ NotificationRecipients::BuildService.build_merge_request_unmergeable_recipients
           │      └─ Builder::MergeRequestUnmergeable.new(merge_request)
           │             ├─ build!: merge_participants.each do add_recipients
           │             └─ acting_user: nil (誰も除外しない)
           │
           └─ Notify.merge_request_unmergeable_email(recipient_id, mr_id)
                  ├─ setup_merge_request_mail
                  │      ├─ MergeRequest.find
                  │      ├─ User.find(recipient_id)
                  │      └─ SentNotification.record
                  └─ mail_answer_thread(@merge_request.author_id)
                         └─ deliver_later
```

### データフロー図

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

MergeRequest ───────────▶ NotificationService
(unmergeable)            .merge_request_unmergeable
                              │
                              ▼
                         merge_request_unmergeable_email
                              │
                              ▼
                         build_merge_request_unmergeable_recipients
                              │
                              ▼
                         MergeRequestUnmergeable.build! ──▶ recipients[merge_participants]
                              │
                              ▼
                         Notify.merge_request_unmergeable_email
                              │
                              ▼
                         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` | ソース | 受信者決定サービス |
| merge_request_unmergeable.rb | `app/services/notification_recipients/builder/merge_request_unmergeable.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` | ソース | メーラー基底クラス |
| merge_request.rb | `app/models/merge_request.rb` | ソース | merge_participantsメソッド |
| merge_request_unmergeable_email.text.haml | `app/views/notify/merge_request_unmergeable_email.text.haml` | テンプレート | テキストメール本文 |
| merge_request_unmergeable_email.html.haml | `app/views/notify/merge_request_unmergeable_email.html.haml` | テンプレート | HTMLメール本文 |
