# 通知設計書 12-import_issues_csv_email

## 概要

本ドキュメントは、GitLabにおけるCSVファイルからのIssueインポート処理が完了した際に送信されるメール通知「import_issues_csv_email」の設計仕様を定義するものである。

### 本通知の処理概要

この通知は、ユーザーがCSVファイルを使用してIssueを一括インポートした後、その処理結果（成功件数、エラー情報等）をメールで通知する機能である。

**業務上の目的・背景**：大規模なプロジェクト移行や、外部ツールからのIssue移行時に、CSVファイルを使用した一括インポート機能が利用される。インポート処理は非同期で行われるため、処理完了後にユーザーへ結果を通知する必要がある。この通知により、ユーザーはインポートの成功件数やエラーの有無を確認し、必要に応じて修正・再インポートを行うことができる。

**通知の送信タイミング**：CSVインポート処理が完了した直後に送信される。Issues::ImportCsvServiceのexecuteメソッド実行後、email_results_to_userメソッドにより通知が発行される。

**通知の受信者**：インポート操作を実行したユーザー本人のみが受信者となる。インポートはユーザーが明示的に開始する操作であり、その結果は操作者のみに通知される。

**通知内容の概要**：メールには、対象プロジェクト名、インポート成功件数、エラーが発生した行番号、CSVパースエラーの有無、マイルストーン等の前処理エラー情報が含まれる。

**期待されるアクション**：受信者は通知を確認し、インポート結果を把握する。エラーがあった場合は、該当行を修正してCSVファイルを再インポートする。

## 通知種別

メール通知

## 送信仕様

### 基本情報

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

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

受信者はインポート操作を実行したユーザー本人のみ。`@user.notification_email_for(@project.group)`により、プロジェクトのグループに対する通知用メールアドレスが使用される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabインスタンスのデフォルト送信元アドレス |
| 送信元名称 | GitLabインスタンス名 |
| 件名 | "{プロジェクト名} \| Imported issues" |
| 形式 | HTML/テキスト（マルチパート） |

### 本文テンプレート

**HTMLバージョン**
```html
Your CSV import for project {project_link} has been completed.

{success_count} issue(s) imported.

[エラーがある場合]
Errors found on line(s): {error_lines}. Please check if these lines have an issue title.

[パースエラーがある場合]
Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.

[マイルストーンエラーがある場合]
Could not find the following {column} values in {project}{parent_groups_clause}: {error_lines}

[エラーがある場合]
Please fix the errors above and try the CSV import again.
```

**テキストバージョン**
```
Your CSV import for project {project_name} ({project_url}) has been completed.

{success_count} issue(s) imported.

[エラーがある場合]
Errors found on line number(s): {error_lines}. Please check if these lines have an issue title.

[パースエラーがある場合]
Error parsing CSV file. Please make sure it has the correct format: a delimited text file that uses a comma to separate values.

[マイルストーンエラーがある場合]
Could not find the following {column} values in {project}{parent_groups_clause}: {error_lines}

[エラーがある場合]
Please fix the errors above and try the CSV import again.
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @user | インポート実行ユーザー | User.find(user_id) | Yes |
| @project | 対象プロジェクト | Project.find(project_id) | Yes |
| @results | インポート結果ハッシュ | ImportCsvService実行結果 | Yes |
| @results[:success] | 成功件数 | インポート処理のカウンター | Yes |
| @results[:error_lines] | エラー行番号配列 | インポート処理のエラーログ | No |
| @results[:parse_error] | パースエラーフラグ | CSVパース結果 | No |
| @results[:preprocess_errors] | 前処理エラー情報 | マイルストーン等の検証結果 | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バックグラウンドジョブ | CSVインポート処理完了 | インポート処理が正常に終了した場合 | Issues::ImportCsvService.executeの完了後 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| プロジェクトのメール無効設定 | プロジェクトでメール通知が無効化されている場合（ただし、この通知は結果通知のため通常は送信される） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[CSVインポート開始] --> B[Issues::ImportCsvService.execute]
    B --> C[CSVパース]
    C --> D{パース成功?}
    D -->|No| E[parse_error設定]
    D -->|Yes| F[各行処理ループ]
    F --> G{Issue作成成功?}
    G -->|Yes| H[success カウントアップ]
    G -->|No| I[error_lines に追加]
    H --> J{次の行あり?}
    I --> J
    J -->|Yes| F
    J -->|No| K[結果集計]
    E --> K
    K --> L[email_results_to_user呼び出し]
    L --> M[Notify.import_issues_csv_email]
    M --> N[deliver_later]
    N --> O[Sidekiqキューに追加]
    O --> P[非同期でメール送信]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| users | インポート実行ユーザー情報取得 | |
| projects | プロジェクト情報取得 | |
| namespaces | 名前空間情報取得（グループ名等） | |
| milestones | マイルストーン検証用 | preprocess_errorsの検出 |
| issues_csv_imports | インポート履歴記録 | |

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

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別子 | 引数で指定 |
| notification_email | 通知先メールアドレス | |

#### projects

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | プロジェクト識別子 | 引数で指定 |
| name | プロジェクト名 | メール件名に使用 |
| full_name | フルパス名 | メール本文に使用 |
| group_id | グループID | 通知メールアドレス決定に使用 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| issues_csv_imports | INSERT | インポート履歴の記録（ImportCsvService内） |

#### issues_csv_imports

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | user_id | インポート実行ユーザーID | |
| INSERT | project_id | 対象プロジェクトID | |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | SMTPサーバー接続失敗 | Sidekiqによる自動リトライ |
| ユーザー不存在 | User.find失敗 | ActiveRecord::RecordNotFoundが発生 |
| プロジェクト不存在 | Project.find失敗 | ActiveRecord::RecordNotFoundが発生 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 特定のレート制限なし（結果通知のため） |
| 1日あたり上限 | 特定のレート制限なし |

### 配信時間帯

特定の時間帯制限なし（インポート完了後即時配信）

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

- インポート結果はインポート実行者本人のみに通知される
- プロジェクト名やエラー情報が含まれるため、メール傍受に注意が必要
- CSVファイル自体はメールに添付されない

## 備考

- email_with_layoutを使用してメーラーレイアウトが適用される
- インポートエラーの詳細（行番号、エラー種別）が通知に含まれるため、ユーザーは修正箇所を特定しやすい
- マイルストーンが見つからない場合のエラーメッセージには、親グループの検索範囲も含まれる

---

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

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

### 推奨読解順序

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

インポート結果のデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | base_service.rb | `app/services/issuable/import_csv/base_service.rb` | resultsハッシュの構造（:success, :error_lines, :parse_error, :preprocess_errors） |

**読解のコツ**: resultsハッシュは複数のキーを持ち、各キーの意味と値の型を理解することで、テンプレートの条件分岐が明確になる。

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

処理の起点となるImportCsvServiceを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | import_csv_service.rb | `app/services/issues/import_csv_service.rb` | email_results_to_userメソッド（11-13行目）でNotify.import_issues_csv_emailを呼び出し |

**主要処理フロー**:
1. **5行目**: `def execute` - エントリーポイント
2. **6行目**: `record_import_attempt` - インポート履歴記録
3. **8行目**: `super` - 基底クラスの実行（CSVパース、Issue作成）
4. **11-13行目**: `email_results_to_user` - メール送信

#### Step 3: メーラーモジュールを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | issues.rb | `app/mailers/emails/issues.rb` | import_issues_csv_emailメソッド（167-175行目） |
| 3-2 | notify.rb | `app/mailers/notify.rb` | email_with_layoutメソッド（276-281行目） |

**主要処理フロー**:
- **167行目**: `def import_issues_csv_email(user_id, project_id, results)`
- **168-170行目**: @user, @project, @resultsの設定
- **172-174行目**: `email_with_layout`でメール送信

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | import_issues_csv_email.html.haml | `app/views/notify/import_issues_csv_email.html.haml` | HTML形式のメール本文。エラー種別による条件分岐を確認 |
| 4-2 | import_issues_csv_email.text.erb | `app/views/notify/import_issues_csv_email.text.erb` | テキスト形式のメール本文 |

**主要処理フロー**:
- **4-6行目**: プロジェクトリンクと完了メッセージ
- **8-10行目**: 成功件数の表示（n_による複数形対応）
- **12-15行目**: エラー行の表示
- **17-19行目**: パースエラーの表示
- **21-30行目**: マイルストーンエラーの表示
- **32-34行目**: 再インポート案内

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

```
[UI/API] CSVインポートリクエスト
    |
    └── ImportIssuesCsvWorker (Sidekiq)
            |
            └── Issues::ImportCsvService#execute
                    |
                    ├── record_import_attempt
                    |       └── Issues::CsvImport.create!
                    |
                    ├── super (Issuable::ImportCsv::BaseService)
                    |       ├── CSV.parse
                    |       └── Issues::CreateService (for each row)
                    |
                    └── email_results_to_user
                            |
                            └── Notify.import_issues_csv_email
                                    |
                                    ├── User.find
                                    ├── Project.find
                                    └── email_with_layout
                                            |
                                            └── deliver_later (Sidekiq)
```

### データフロー図

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

CSVファイル        ──┐
                    ├──▶ ImportCsvService     ──▶ Issue作成（複数）
User               ──┤           │
                    │           ▼
Project            ──┤    results集計
                    │           │
                    │           ├── :success (count)
                    │           ├── :error_lines (array)
                    │           ├── :parse_error (boolean)
                    │           └── :preprocess_errors (hash)
                    │           │
                    │           ▼
                    └──▶ Notify.import_issues_csv_email
                                │
                                └──▶ ActionMailer::MessageDelivery
                                            │
                                            ▼
                                      SMTPサーバー ──▶ ユーザーメールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| import_csv_service.rb | `app/services/issues/import_csv_service.rb` | ソース | CSVインポートサービス |
| base_service.rb | `app/services/issuable/import_csv/base_service.rb` | ソース | インポートサービス基底クラス |
| issues.rb | `app/mailers/emails/issues.rb` | ソース | Issue関連メール生成モジュール |
| notify.rb | `app/mailers/notify.rb` | ソース | メーラー基底クラス |
| import_issues_csv_email.html.haml | `app/views/notify/import_issues_csv_email.html.haml` | テンプレート | HTML形式メールテンプレート |
| import_issues_csv_email.text.erb | `app/views/notify/import_issues_csv_email.text.erb` | テンプレート | テキスト形式メールテンプレート |
| csv_import.rb | `app/models/issues/csv_import.rb` | ソース | CSVインポート履歴モデル |
