# 通知設計書 6-relabeled_issue_email

## 概要

本ドキュメントは、GitLabにおけるIssueのラベル変更通知メール機能（relabeled_issue_email）の設計仕様を定義する。

### 本通知の処理概要

relabeled_issue_emailは、GitLabプロジェクト内のIssueにラベルが追加された際に、そのラベルを購読しているユーザーに対してメール通知を送信する機能である。

**業務上の目的・背景**：プロジェクト管理において、ラベルはIssueの分類、優先度、ステータス、担当チームなどを示す重要なメタデータである。特定のラベルを購読しているユーザーは、そのカテゴリに関連する新しいIssueや、既存Issueへのラベル付与を把握する必要がある。この通知により、関心のあるカテゴリのIssueを漏れなく追跡できる。

**通知の送信タイミング**：Issueにラベルが追加された直後に、非同期（Sidekiq経由）でメール送信がトリガーされる。NotificationService.new.relabeled_issue(issue, added_labels, current_user)が呼び出されることで処理が開始される。ラベルの削除時には通知は送信されない。

**通知の受信者**：追加されたラベルを購読（subscribe）しているユーザーのみが受信対象となる。ラベル変更を実行したユーザー自身は通知対象から除外される。

**通知内容の概要**：追加されたラベル名のリストが含まれる。「Label added: {label_name}」または「Labels added: {label_names}」というメッセージが表示される。

**期待されるアクション**：受信者は関心のあるラベルが付与されたIssueを確認し、必要に応じてコメント追加、担当引き受け、作業着手などのアクションを行うことが期待される。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（Sidekiq deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqデフォルト設定（最大25回） |

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

relabeled_resource_emailメソッドを使用して受信者を決定する。以下の特徴がある：

1. 追加されたラベルのsubscribers（購読者）を取得
2. 各ラベルに対してlabel.subscribers(project)を呼び出し
3. notifiable_usersで通知可能なユーザーにフィルタリング
4. :subscriptionレベルで通知
5. 現在のユーザー（ラベル変更実行者）は通知対象から除外される（acting_user設定）

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | `{sender.name} ({sender.to_reference})` <gitlab@{host}> |
| 送信元名称 | ラベル変更実行者名（@ユーザー名） |
| 件名 | `Re: {project_name} | {issue_title} (#{iid})` |
| 形式 | HTML/テキスト両対応（multipart） |

### 本文テンプレート

```
Label added: {label_name} （単一ラベルの場合）
Labels added: {label_name1}, {label_name2}, ... （複数ラベルの場合）
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @issue | Issueオブジェクト | Issue.find(issue_id) | Yes |
| @project | プロジェクトオブジェクト | @issue.project | Yes |
| @namespace | 名前空間 | @issue.namespace | Yes |
| @target_url | IssueへのURL | Gitlab::UrlBuilder.build(@issue) | Yes |
| @recipient | 受信者ユーザー | User.find(recipient_id) | Yes |
| @sent_notification | 送信通知記録 | SentNotification.record | Yes |
| @label_names | 追加されたラベル名リスト | labels.map(&:name) | Yes |
| @labels_url | ラベル購読設定画面URL | project_labels_url/group_labels_url | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | Issue編集画面でラベル追加 | ラベルが追加された | WebUIからのラベル追加 |
| API | PUT /api/v4/projects/:id/issues/:issue_iid | labelsの追加 | REST API経由のラベル追加 |
| クイックアクション | /label コマンド | コマンド実行成功 | コメント経由のラベル追加 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| project.emails_disabled? == true | プロジェクトでメール通知が無効化されている場合 |
| 受信者の通知レベルがsubscription未満 | ユーザー設定で購読通知を無効化している場合 |
| レート制限超過 | プロジェクト/グループ単位の通知レート制限に達した場合 |
| ラベル削除のみ | ラベルが削除されたのみで追加がない場合 |
| 購読者なし | 追加されたラベルに購読者がいない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Issueラベル追加] --> B[NotificationService.relabeled_issue]
    B --> C[relabeled_resource_email呼び出し]
    C --> D[追加ラベルのsubscribers取得]
    D --> E{購読者あり?}
    E -->|No| F[終了]
    E -->|Yes| G[notifiable_usersでフィルタ]
    G --> H[各受信者に対してループ]
    H --> I[Notify.relabeled_issue_email.deliver_later]
    I --> J[Sidekiqキューに追加]
    J --> K[setup_issue_mail実行]
    K --> L[@label_names設定]
    L --> M[@labels_url設定]
    M --> N[mail_answer_thread実行]
    N --> O[メール送信]
    O --> P[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| issues | Issue情報取得 | メール本文データ |
| users | ユーザー情報取得 | 作成者・受信者 |
| labels | ラベル情報 | 追加されたラベル取得 |
| subscriptions | 購読情報 | ラベル購読者取得 |
| projects | プロジェクト情報 | メール件名・ヘッダー |
| namespaces | 名前空間情報 | グループ情報取得 |

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

#### labels

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ラベル識別 | PRIMARY KEY |
| title | ラベル名 | メール本文に使用 |
| project_id | プロジェクトID | プロジェクトラベル |
| group_id | グループID | グループラベル |

#### subscriptions

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| subscribable_type | 購読対象タイプ | 'Label' |
| subscribable_id | 購読対象ID | ラベルID |
| user_id | 購読者ID | 受信者取得 |
| subscribed | 購読フラグ | true |

### 更新テーブル一覧

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

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTP接続エラー | Sidekiqリトライ |
| テンプレートエラー | Hamlレンダリング失敗 | エラーログ出力 |
| 宛先不正 | メールアドレス形式不正 | 送信スキップ |
| Issue削除済み | 送信前にIssueが削除 | RecordNotFound例外 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Gitlab::ApplicationRateLimiter設定による |
| 1日あたり上限 | プロジェクト/グループ単位で設定 |

### 配信時間帯

制限なし（24時間送信可能）

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

- 機密Issue（confidential: true）の場合、X-GitLab-ConfidentialIssueヘッダーがtrueに設定される
- 受信者が当該Issueへのアクセス権限を持つかの確認はnotifiable_usersで実施
- メール本文にはラベル購読設定画面へのURLが含まれ、購読解除が可能

## 備考

- ラベル削除時には通知は送信されない（追加時のみ）
- @labels_urlにはsubscribed: trueパラメータが付与され、購読中ラベルページへリンク
- プロジェクトラベルの場合はproject_labels_url、グループラベルの場合はgroup_labels_urlを使用
- テンプレートは_relabeled_issuable_email.html.hamlを使用（Issue/MergeRequest共通）
- 複数ラベル追加時はto_sentenceでカンマ区切りリスト化

---

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

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

### 推奨読解順序

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

まず、ラベルと購読関連のデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | label.rb | `app/models/label.rb` | has_many :subscriptions、subscribersメソッド |
| 1-2 | subscription.rb | `app/models/subscription.rb` | 購読関連テーブルの定義 |

**読解のコツ**: Labelモデルのsubscribersメソッドが購読者を取得するロジックの核心。

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

処理の起点となるNotificationServiceのrelabeled_issueメソッドを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_service.rb | `app/services/notification_service.rb` | relabeled_issueメソッド（269-271行目）、relabeled_resource_email（847-860行目） |

**主要処理フロー**:
1. **269行目**: `def relabeled_issue(issue, added_labels, current_user)` - 公開メソッド定義
2. **270行目**: `relabeled_resource_email(issue, added_labels, current_user, :relabeled_issue_email)` 呼び出し
3. **847-860行目**: relabeled_resource_emailで購読者取得・メール送信

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

実際のメール生成処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | issues.rb | `app/mailers/emails/issues.rb` | relabeled_issue_emailメソッド（71-89行目） |

**主要処理フロー**:
- **71行目**: `def relabeled_issue_email(recipient_id, issue_id, label_names, updated_by_user_id, reason = nil)`
- **74-78行目**: @labels_url設定（プロジェクト/グループで分岐）
- **80-88行目**: mail_answer_thread実行

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

メール本文のレンダリング処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | _relabeled_issuable_email.html.haml | `app/views/notify/_relabeled_issuable_email.html.haml` | 共通テンプレート（全3行） |

**主要処理フロー**:
- **2行目**: `n_('Label added: %{labels}', 'Labels added: %{labels}', @label_names.size)`でn_関数による複数形対応

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

```
Issues::UpdateService
    │
    └─ NotificationService#relabeled_issue (notification_service.rb:269)
           │
           └─ #relabeled_resource_email (notification_service.rb:847)
                  │
                  ├─ labels.flat_map { |l| l.subscribers(project) }.uniq
                  │
                  ├─ notifiable_users(recipients, :subscription, ...)
                  │
                  ├─ label_names = labels.map(&:name)
                  │
                  └─ recipients.each { Notify.relabeled_issue_email.deliver_later }
                         │
                         └─ Notify#relabeled_issue_email (emails/issues.rb:71)
                                │
                                ├─ #setup_issue_mail
                                │
                                ├─ @label_names設定
                                │
                                ├─ @labels_url設定（project/group分岐）
                                │
                                └─ #mail_answer_thread
```

### データフロー図

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

Issueラベル追加 ────────────▶ Issues::UpdateService ─────────▶ added_labels
     │                            │                              │
     │                            ▼                              │
     │                  NotificationService                      │
     │                  #relabeled_issue                         │
     │                            │                              │
     │                            ▼                              │
     │                  labels.subscribers ─────────────────────▶ 購読者リスト
     │                            │                              │
     │                            ▼                              │
     └──────────────────▶ Emails::Issues ─────────────────────────┤
                          #relabeled_issue_email                 │
                                │                                │
                                ├─ @label_names                  │
                                ├─ @labels_url                   │
                                │                                │
                                ▼                                ▼
                        SentNotification ──────────▶ p_sent_notifications
                        (記録作成)                    テーブル
                                │
                                ▼
                        ActionMailer ────────────────▶ SMTPサーバー
                        (メール送信)                   └─▶ 購読者メールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | サービス | 通知処理のエントリーポイント（269-271行目、847-860行目） |
| issues.rb | `app/mailers/emails/issues.rb` | メーラー | relabeled_issue_emailメソッド定義（71-89行目） |
| notify.rb | `app/mailers/notify.rb` | メーラー | 共通メール処理・ヘッダー設定 |
| _relabeled_issuable_email.html.haml | `app/views/notify/_relabeled_issuable_email.html.haml` | テンプレート | ラベル変更通知共通テンプレート |
| label.rb | `app/models/label.rb` | モデル | Labelデータモデル、subscribersメソッド |
| subscription.rb | `app/models/subscription.rb` | モデル | 購読テーブルモデル |
