# 機能設計書 138-Web通知

## 概要

本ドキュメントは、GitLabにおけるWeb通知（ToDo機能）の設計について記述する。ユーザーに対してプロジェクトやグループでの活動をWeb UIで通知し、タスクの追跡と管理を実現する機能を定義する。

### 本機能の処理概要

**業務上の目的・背景**：ソフトウェア開発チームでは、自分に関連するタスクや確認事項を一元管理することが重要である。Web通知（ToDo）機能により、担当者への割り当て、メンション、レビュー依頼等のアクションをToDoリストとして管理し、作業の見逃しを防止する。

**機能の利用シーン**：
- イシューやマージリクエストに担当者として割り当てられた時
- コメントでメンションされた時
- マージリクエストのレビュアーに指定された時
- ビルドが失敗した時
- マージリクエストがマージ不可になった時
- メンバーアクセスリクエストがあった時
- SSHキーの期限切れ警告時

**主要な処理内容**：
1. ToDo作成イベントの検知
2. ToDo受信者の決定
3. ToDoレコードの作成
4. GraphQL Subscription経由のリアルタイム更新
5. ToDoの完了・復元処理
6. ToDoのスヌーズ処理

**関連システム・外部連携**：
- ActionCable（WebSocket通信）
- GraphQL Subscriptions（リアルタイム更新）

**権限による制御**：
- ToDoはターゲットオブジェクトの権限に基づいて表示制御
- プライベートプロジェクトのToDoはメンバーのみ閲覧可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | ToDoリスト | 主機能 | ToDoの一覧表示・管理 |
| 24 | プロジェクト詳細 | 補助機能 | プロジェクトのToDo作成 |
| 27 | イシュー詳細 | 補助機能 | イシューのToDo作成 |
| 43 | マージリクエスト詳細 | 補助機能 | MRのToDo作成 |

## 機能種別

タスク管理 / リアルタイム通知 / WebSocket / データ表示

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| target_id | Integer | Yes | ターゲットオブジェクトID | - |
| target_type | String | Yes | ターゲットタイプ | Issue/MergeRequest等 |
| action | Integer | Yes | アクション種別 | 1-18の値 |
| author_id | Integer | Yes | 作成者ID | - |

### 入力データソース

- イベント発生元オブジェクト（Issue, MergeRequest, Note等）
- メンションパーサーからのユーザー抽出

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | ToDoID |
| action | Integer | アクション種別 |
| author | Object | 作成者 |
| target | Object | ターゲットオブジェクト |
| state | String | 状態（pending/done） |
| created_at | DateTime | 作成日時 |
| snoozed_until | DateTime | スヌーズ期限 |

### 出力先

- todosテーブルへのレコード保存
- GraphQL Subscription経由でクライアントにプッシュ
- GitLab UIでのToDoリスト表示

## 処理フロー

### 処理シーケンス

```
1. イベント発生検知
   └─ TodoService.new.{event_method}が呼ばれる
2. 受信者決定
   └─ メンション、担当者、レビュアー等から受信者を決定
3. 重複チェック
   └─ 既存の未完了ToDoがないか確認
4. ToDo作成
   └─ bulk_insert_todosでバッチ挿入
5. カウント更新
   └─ Users::UpdateTodoCountCacheServiceでキャッシュ更新
6. リアルタイム通知
   └─ GraphqlTriggers.issuable_todo_updatedで購読者に通知
```

### フローチャート

```mermaid
flowchart TD
    A[イベント発生] --> B[TodoService呼び出し]
    B --> C{アクション種別}
    C -->|担当者割当| D[create_assignment_todo]
    C -->|メンション| E[create_mention_todos]
    C -->|レビュー依頼| F[create_reviewer_todo]
    C -->|ビルド失敗| G[create_build_failed_todo]
    D --> H[受信者フィルタ]
    E --> H
    F --> H
    G --> H
    H --> I[権限チェック]
    I -->|OK| J[重複チェック]
    I -->|NG| K[スキップ]
    J -->|新規| L[bulk_insert_todos]
    J -->|既存| K
    L --> M[UpdateTodoCountCacheService]
    M --> N[GraphqlTriggers.issuable_todo_updated]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-138-01 | アクション種別 | 12種類のアクションでToDo作成 | ToDo作成時 |
| BR-138-02 | 重複防止 | 同一ユーザー/ターゲット/アクションの重複ToDoは作成しない | ToDo作成時（一部例外あり） |
| BR-138-03 | 自動完了 | 対象オブジェクトに対するアクションでToDoを自動完了 | 更新/クローズ時 |
| BR-138-04 | 削除待機 | 非表示ToDoは1時間待ってから削除 | アクセス権限喪失時 |
| BR-138-05 | スヌーズ機能 | ToDoを一時的に非表示にする | ユーザー操作時 |

### 計算ロジック

- **アクション種別**: ASSIGNED(1), MENTIONED(2), BUILD_FAILED(3), MARKED(4), APPROVAL_REQUIRED(5), UNMERGEABLE(6), DIRECTLY_ADDRESSED(7), MERGE_TRAIN_REMOVED(8), REVIEW_REQUESTED(9), MEMBER_ACCESS_REQUESTED(10), REVIEW_SUBMITTED(11), OKR_CHECKIN_REQUESTED(12), ADDED_APPROVER(13), SSH_KEY_EXPIRED(14), SSH_KEY_EXPIRING_SOON(15), DUO_*_ACCESS_GRANTED(16-18)
- **状態**: pending, done
- **複数許可アクション**: MENTIONED, DIRECTLY_ADDRESSED, MEMBER_ACCESS_REQUESTED

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ToDo作成 | todos | INSERT | ToDoレコード作成 |
| ToDo取得 | todos | SELECT | ToDoリスト取得 |
| ToDo完了 | todos | UPDATE | state更新 |
| ToDo復元 | todos | UPDATE | state更新 |
| カウント更新 | users | UPDATE | todos_count更新 |

### テーブル別操作詳細

#### todos

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id, author_id, target_id, target_type, action, state | ToDo情報 | bulk_insertで一括挿入 |
| SELECT | user_id, state | pending/done状態 | ソート: created_at DESC |
| UPDATE | state, resolved_by_action, snoozed_until | done/pending | batch_updateで一括更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ValidationError | 必須フィールド不足 | バリデーションエラー返却 |
| 403 | Forbidden | 権限なし | 適切な権限を付与 |
| 404 | Not Found | 対象オブジェクトが存在しない | 正しいIDを指定 |

### リトライ仕様

該当なし（同期処理）

## トランザクション仕様

- ToDo作成はバッチ挿入で効率的に処理
- カウント更新は別サービスで非同期実行

## パフォーマンス要件

- ToDoリスト取得は100件以下で200ms以内
- バッチサイズ100件でbulk_insert
- ToDoカウントはキャッシュを使用

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

- ToDoの表示はターゲットオブジェクトの読み取り権限に基づく
- 内部メモのToDoは機密情報として扱う
- banned_usersのToDoは非表示

## 備考

- ToDoのWAIT_FOR_DELETE期間は1時間
- parentless_action_types（SSH_KEY_EXPIRED等）はプロジェクト/グループに紐づかない
- GraphQL Subscriptionでリアルタイム更新を実現

---

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

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

### 推奨読解順序

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

Web通知の中核となるToDoモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | todo.rb | `app/models/todo.rb` | ToDoのデータ構造、アクション種別 |

**読解のコツ（todo.rb）**:
- **11行目**: WAIT_FOR_DELETE = 1.hour - 削除待機時間
- **14-31行目**: アクション定数定義（ASSIGNED, MENTIONED等）
- **34-47行目**: ACTION_NAMESハッシュでアクション名をマッピング
- **49行目**: ACTIONS_MULTIPLE_ALLOWED - 重複許可アクション
- **52-55行目**: PARENTLESS_ACTION_TYPES - プロジェクト不要アクション
- **57行目**: BATCH_DELETE_SIZE = 100
- **91-115行目**: スコープ定義（pending, done, for_project等）
- **116行目**: resolved_by_action enum
- **118-125行目**: state_machine定義（pending -> done）
- **341-343行目**: action_nameメソッド

#### Step 2: サービス層を理解する

ToDo作成のビジネスロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | todo_service.rb | `app/services/todo_service.rb` | ToDo作成・完了サービス |

**読解のコツ（todo_service.rb）**:
- **13行目**: BATCH_SIZE = 100
- **20-22行目**: new_issueメソッド
- **60-62行目**: reassigned_assignableメソッド - 担当者変更
- **77-79行目**: new_merge_requestメソッド
- **148-150行目**: new_noteメソッド
- **204-212行目**: mark_todoメソッド - 手動ToDo作成
- **219-225行目**: resolve_todos_for_targetメソッド - ToDo完了
- **227-233行目**: resolve_todosメソッド - バッチ完了
- **324-340行目**: create_todosメソッド - ToDo一括作成
- **352-365行目**: bulk_insert_todosメソッド

#### Step 3: コントローラーを理解する

ToDoリスト表示のエントリーポイント。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | todos_controller.rb | `app/controllers/dashboard/todos_controller.rb` | ToDoダッシュボード |

**読解のコツ（todos_controller.rb）**:
- **6行目**: feature_category :notifications
- **9-13行目**: indexアクション - ToDoリスト表示

#### Step 4: リアルタイム更新を理解する

GraphQL SubscriptionとActionCableの連携。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | graphql_channel.rb | `app/channels/graphql_channel.rb` | GraphQL WebSocket |

**読解のコツ（graphql_channel.rb）**:
- **7-31行目**: subscribedメソッド - サブスクリプション登録
- **33-39行目**: unsubscribedメソッド - サブスクリプション解除
- **52-62行目**: contextメソッド - 認証コンテキスト

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

```
TodoService
    │
    ├─ new_issue / new_merge_request / new_note / etc.
    │      │
    │      ├─ new_issuable
    │      │      ├─ create_assignment_todo
    │      │      ├─ create_reviewer_todo
    │      │      └─ create_mention_todos
    │      │
    │      └─ handle_note
    │             ├─ resolve_todos_for_target
    │             └─ create_mention_todos
    │
    ├─ create_todos(users, attributes, namespace, project)
    │      ├─ excluded_user_ids (重複チェック)
    │      ├─ bulk_insert_todos
    │      │      └─ Todo.insert_all
    │      └─ Users::UpdateTodoCountCacheService
    │
    ├─ resolve_todos_for_target
    │      ├─ pending_todos
    │      │      └─ PendingTodosFinder
    │      ├─ batch_update
    │      └─ GraphqlTriggers.issuable_todo_updated
    │
    └─ mark_todo (手動ToDo作成)

Todo (モデル)
    │
    ├─ state_machine (pending -> done)
    │
    ├─ ACTION_NAMES (アクション名マッピング)
    │
    ├─ target_url (対象URLの生成)
    │
    └─ スコープ
           ├─ pending / done
           ├─ for_user / for_project / for_group
           └─ snoozed / not_snoozed

GraphqlChannel
    │
    ├─ subscribed (サブスクリプション登録)
    │      └─ GitlabSchema.execute
    │
    └─ unsubscribed
           └─ GitlabSchema.subscriptions.delete_subscription
```

### データフロー図

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

イベント発生 ────────────────▶ TodoService ──────────────────▶ todos テーブル
(Issue作成、メンション等)           │                              │
                                   ▼                              ▼
                          受信者決定・フィルタ            UpdateTodoCountCache
                                   │                              │
                                   ▼                              ▼
                          重複チェック                    ユーザーカウント更新
                                   │
                                   ▼
                          bulk_insert_todos
                                   │
                                   ▼
                          GraphqlTriggers
                                   │
                                   ▼
                          GraphQL Subscription
                                   │
                                   ▼
                          ActionCable / WebSocket
                                   │
                                   ▼
                          クライアント更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| todo.rb | `app/models/todo.rb` | モデル | ToDoモデル |
| todo_service.rb | `app/services/todo_service.rb` | サービス | ToDo作成・管理 |
| todos_controller.rb | `app/controllers/dashboard/todos_controller.rb` | コントローラー | ToDoダッシュボード |
| todos_controller.rb | `app/controllers/projects/todos_controller.rb` | コントローラー | プロジェクトToDo |
| graphql_channel.rb | `app/channels/graphql_channel.rb` | チャンネル | GraphQL WebSocket |
| snoozing_service.rb | `app/services/todos/snoozing_service.rb` | サービス | スヌーズ処理 |
| allowed_target_filter_service.rb | `app/services/todos/allowed_target_filter_service.rb` | サービス | 権限フィルタ |
| pending_todos_finder.rb | `app/finders/pending_todos_finder.rb` | ファインダー | 未完了ToDo検索 |
| update_todo_count_cache_service.rb | `app/services/users/update_todo_count_cache_service.rb` | サービス | カウント更新 |
| graphql_triggers.rb | `app/graphql/graphql_triggers.rb` | トリガー | リアルタイム通知 |
