# 通知設計書 33-note_commit_email

## 概要

本ドキュメントは、GitLabにおけるコミットへのコメント通知（note_commit_email）の設計を記述したものである。

### 本通知の処理概要

**業務上の目的・背景**：コミットに対してコメントを残すことは、コードの特定の変更に関するフィードバックや質問を行う重要な手段である。コミットにコメントが追加された際に関係者に通知することで、コードレビューのやり取りやナレッジ共有を迅速化できる。特にコミットは複数のMerge Requestに関連する可能性があるため、コミット単位での通知は有用である。

**通知の送信タイミング**：ユーザーがコミットにコメント（Note）を追加した時点で送信される。NotificationService#new_noteを経由し、noteable_typeに応じてnote_commit_emailが選択される。

**通知の受信者**：コメントが追加されたコミットに関連する以下のユーザーが受信者となる。コミットの作成者（Author）、既存のディスカッション参加者、メンションされたユーザー、およびプロジェクトのwatch設定を持つユーザー。受信者はNotificationRecipients::BuildService.build_new_note_recipientsによって決定される。

**通知内容の概要**：コメントが追加されたコミットの情報（SHA、コミットメッセージ）、コメントの内容、コメント投稿者の情報が含まれる。ディスカッション形式の場合は、どのファイルのどの行に関するコメントかも表示される。

**期待されるアクション**：受信者はコメント内容を確認し、必要に応じて返信を行う。コードの問題を指摘された場合は修正を検討する。ディスカッションに参加して議論を深める。

## 通知種別

メール

## 送信仕様

### 基本情報

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

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

NotificationRecipients::BuildService.build_new_note_recipientsを使用して受信者を決定する。具体的には以下の条件でフィルタリングされる：

1. コミットの作成者
2. 既存のディスカッション参加者（同スレッドにコメントした人）
3. コメント内でメンションされたユーザー
4. プロジェクトのwatch設定を持つユーザー
5. 通知が無効化されていないユーザー（Disabled以外）
6. システムノート（自動生成コメント）の場合は除外

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLab設定のデフォルト送信元 |
| 送信元名称 | {コメント投稿者名} (@{コメント投稿者ユーザー名}) |
| 件名 | Re: {プロジェクト名} \| {コミットタイトル} ({コミット参照}) |
| 形式 | HTML/テキスト（両形式） |

### 本文テンプレート

**HTML版：**
```
{コメント投稿者名} commented:（または started a new discussion / commented on a discussion）
{コメント対象ファイル・行（diff discussionの場合）}

{コード差分プレビュー（該当する場合）}

{コメント本文（Markdown形式）}
```

**テキスト版：**
```
{コメント投稿者名} commented: {コメントURL}

（diff discussionの場合）
> {差分行}

{コメント本文}
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @note | Noteオブジェクト | Note.find(note_id) | Yes |
| @commit | コミットオブジェクト | @note.noteable | Yes |
| @project | プロジェクト | @note.project | Yes |
| @target_url | コメントへのURL | project_commit_url + anchor | Yes |
| @recipient | 受信者ユーザー | User.find(recipient_id) | Yes |
| discussion | ディスカッションオブジェクト | @note.discussion | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | コミット詳細画面でコメント投稿 | コメントが正常に保存された場合 | WebUI経由でのコメント |
| API | Notes API呼び出し（コミット向け） | コメントが正常に保存された場合 | API経由でのコメント |
| 画面操作 | diffビューでの行コメント | コメントが正常に保存された場合 | コード行へのコメント |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| システムノート | system_note_with_references?がtrueの場合 |
| noteable_typeが未設定 | コメント対象が不明な場合 |
| 受信者の通知設定がDisabled | ユーザーが通知を無効化している場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[コミットにコメント追加] --> B[Note.create]
    B --> C[NotificationService#new_note]
    C --> D{noteable_type確認}
    D -->|Commit| E[note_commit_email選択]
    D -->|Other| F[他のメール種別]
    E --> G[send_new_note_notifications]
    G --> H[NotificationRecipients::BuildService.build_new_note_recipients]
    H --> I[受信者リスト生成]
    I --> J[各受信者にメール送信]
    J --> K[Notify.note_commit_email.deliver_later]
    K --> L[Sidekiqでメール送信実行]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| notes | コメント情報取得 | コメント本文、投稿者 |
| users | ユーザー情報取得 | コメント投稿者、受信者 |
| projects | プロジェクト情報取得 | 通知設定、diff表示設定 |
| notification_settings | 通知設定取得 | 受信者決定 |
| discussions | ディスカッション情報 | スレッド形式の場合 |

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

#### notes

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | Note識別 | note_id |
| note | コメント本文 | - |
| author_id | コメント投稿者 | - |
| noteable_type | 対象種別 | 'Commit' |
| commit_id | 対象コミットSHA | - |
| project_id | プロジェクト | - |
| discussion_id | ディスカッションID | - |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別 | author_id, recipient_id |
| name | 表示名 | - |
| email | 送信先メールアドレス | notification_email_for使用 |

### 更新テーブル一覧

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

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

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | noteable_type | 'Commit' | - |
| INSERT | noteable_id | commit_id | - |
| INSERT | recipient_id | 受信者ID | - |
| INSERT | reply_key | 生成されたキー | メール返信用 |
| INSERT | in_reply_to_discussion_id | discussion_id | ディスカッションの場合 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPエラー | Sidekiqリトライ |
| テンプレートエラー | 変数が見つからない | 例外ログ記録 |
| 宛先不正 | メールアドレス形式エラー | スキップして次の受信者へ |
| コミット取得失敗 | コミットが削除された | 送信スキップ |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | ApplicationRateLimiter :notification_emails 設定に従う |
| 1日あたり上限 | 設定なし |

### 配信時間帯

制限なし（即座に送信）

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

- 受信者は通知設定に基づいてフィルタリングされ、権限のないユーザーには送信されない
- メール本文にはコメント内容が含まれるため、機密情報を含む場合は注意が必要
- Reply-Toヘッダーには暗号化されたキーが含まれ、メール返信によるコメント追加が可能
- diff表示はプロジェクト設定（show_diff_preview_in_email?）に従う

## 備考

- ディスカッション形式の場合、X-GitLab-Discussion-IDヘッダーが設定される
- diff discussionの場合はコード差分プレビューが含まれる（プロジェクト設定による）
- mail_answer_note_threadを使用してスレッド形式でメール送信

---

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

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

### 推奨読解順序

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

まず、Note（コメント）に関連するデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | note.rb | `app/models/note.rb` | Noteモデルの構造、noteable_type、discussion関連 |
| 1-2 | discussion.rb | `app/models/discussion.rb` | Discussionモデル、diff_discussion判定 |

**読解のコツ**: `noteable_type`と`noteable`ポリモーフィックアソシエーションに注目

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_service.rb | `app/services/notification_service.rb` | new_note、send_new_note_notificationsメソッド |

**主要処理フロー**:
- **469-477行目**: `new_note` - 通知のエントリーポイント
- **471-473行目**: システムノートの除外条件
- **884-891行目**: `send_new_note_notifications` - メール種別決定とループ送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notes.rb | `app/mailers/emails/notes.rb` | note_commit_emailメソッドの実装 |
| 3-2 | notify.rb | `app/mailers/notify.rb` | mail_answer_note_threadの実装 |

**主要処理フロー**:
- **5-11行目**: `note_commit_email` - メール生成
- **6行目**: `setup_note_mail` - 共通セットアップ
- **8行目**: `@commit = @note.noteable` - コミット取得
- **9行目**: `@target_url = project_commit_url(...)` - URL生成
- **10行目**: `mail_answer_note_thread` - スレッド形式でメール送信

#### Step 4: 共通セットアップを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | notes.rb | `app/mailers/emails/notes.rb` | setup_note_mail、note_thread_optionsメソッド |

**主要処理フロー**:
- **92-101行目**: `setup_note_mail` - Note、Project、Recipient設定
- **81-90行目**: `note_thread_options` - メールヘッダー設定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | note_commit_email.html.haml | `app/views/notify/note_commit_email.html.haml` | パーシャル呼び出し |
| 5-2 | _note_email.html.haml | `app/views/notify/_note_email.html.haml` | 共通テンプレート、diff表示 |
| 5-3 | _note_email.text.erb | `app/views/notify/_note_email.text.erb` | テキスト版テンプレート |

**読解のコツ**:
- `render 'note_email'`による共通パーシャルの利用
- `discussion.diff_discussion?`によるdiff表示の分岐

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

```
Note.create (コミットへのコメント)
    │
    └─ NotificationService#new_note
           │
           ├─ system_note_with_references? チェック
           │
           └─ send_new_note_notifications
                  │
                  ├─ notify_method = :note_commit_email (動的決定)
                  │
                  ├─ NotificationRecipients::BuildService.build_new_note_recipients
                  │
                  └─ Notify.note_commit_email
                         │
                         ├─ setup_note_mail
                         │      └─ SentNotification.record_note
                         │
                         └─ mail_answer_note_thread
                                │
                                ├─ Message-ID設定（Note用）
                                ├─ In-Reply-To設定（参照チェーン）
                                ├─ X-GitLab-Discussion-ID（ディスカッションの場合）
                                │
                                └─ deliver_later (Sidekiq)
```

### データフロー図

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

note_id ────────────┬──▶ NotificationService#new_note
                    │            │
recipient_id ───────┤            ▼
                    │    send_new_note_notifications
                    │            │
                    │            ├─ notify_method = :note_commit_email
                    │            │
                    │            ▼
                    │    NotificationRecipients::BuildService
                    │            │
                    │            ▼
                    └──▶ Notify#note_commit_email ───▶ Email (SMTP)
                                │
                                ├─ @note.noteable ───▶ Commit情報
                                │
                                └─ _note_email partial ───▶ 本文生成
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知処理サービス |
| notes.rb | `app/mailers/emails/notes.rb` | ソース | Note関連メーラー |
| notify.rb | `app/mailers/notify.rb` | ソース | メインメーラークラス |
| note_commit_email.html.haml | `app/views/notify/note_commit_email.html.haml` | テンプレート | HTML版メールテンプレート |
| note_commit_email.text.erb | `app/views/notify/note_commit_email.text.erb` | テンプレート | テキスト版メールテンプレート |
| _note_email.html.haml | `app/views/notify/_note_email.html.haml` | テンプレート | 共通Noteパーシャル（HTML） |
| _note_email.text.erb | `app/views/notify/_note_email.text.erb` | テンプレート | 共通Noteパーシャル（テキスト） |
| build_service.rb | `app/services/notification_recipients/build_service.rb` | ソース | 受信者決定サービス |
| note.rb | `app/models/note.rb` | モデル | Noteモデル定義 |
