# 機能設計書 42-イシューリンク

## 概要

本ドキュメントは、GitLabにおけるイシュー間のリレーション（関連付け）機能の設計仕様を定義する。イシューリンクは、複数のイシュー間に「関連（relates to）」「ブロック（blocks）」「ブロックされる（is blocked by）」などのリレーションを設定し、タスク間の依存関係や関連性を可視化する機能である。

### 本機能の処理概要

**業務上の目的・背景**：ソフトウェア開発やプロジェクト管理において、個々のタスク（イシュー）は独立して存在するのではなく、他のタスクと密接に関連していることが多い。例えば、「機能Aを実装する前に機能Bを完了させる必要がある」「バグCとバグDは同じ原因から発生している」といった関係性が存在する。イシューリンク機能は、これらの関係性を明示的に記録し、チームメンバーがタスク間の依存関係を把握しやすくすることで、効率的なプロジェクト進行を支援する。

**機能の利用シーン**：
- 開発者がバグ修正時に、関連する他のバグ報告をリンクして重複を管理する場面
- プロジェクトマネージャーがタスクの依存関係を設定し、作業順序を明確にする場面
- チームメンバーが関連イシューを一覧で確認し、影響範囲を把握する場面
- インシデント対応時に関連するイシューを紐付けて追跡する場面

**主要な処理内容**：
1. イシュー間のリンク作成（関連、ブロック、被ブロック）
2. リンクされたイシューの一覧取得
3. イシューリンクの削除
4. リンク作成時のシステムノート自動追加
5. 親子関係との排他チェック

**関連システム・外部連携**：
- GraphQL APIによるリンク情報の取得・操作
- WorkItemsとの連携（イシューはWorkItemの一種として扱われる）
- インシデント管理機能との連携（インシデントの関連付け追跡）

**権限による制御**：
- リンクの作成・削除：`admin_issue_link`権限（両方のイシューに対してGuest以上の権限が必要）
- リンクの閲覧：イシューの閲覧権限があれば可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 49 | 課題詳細 | 主機能 | 関連イシューのリンク管理 |

## 機能種別

CRUD操作 / リレーション管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| issuable_references | Array[String] | Yes | リンク先イシューの参照（例: #123, project#456） | 有効なイシュー参照形式 |
| link_type | String | No | リンクの種類 | relates_to / blocks / is_blocked_by |
| target_issuable | Issue | No | 直接指定する場合のターゲットイシュー | 存在するイシュー |

### 入力データソース

- 画面入力（イシュー詳細画面のリンク追加フォーム）
- REST API（POST リクエスト）
- GraphQL API（Mutation）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | イシューリンクのID |
| source_id | Integer | リンク元イシューのID |
| target_id | Integer | リンク先イシューのID |
| link_type | String | リンクの種類（relates_to/blocks/is_blocked_by） |
| created_at | DateTime | 作成日時 |
| updated_at | DateTime | 更新日時 |

### 出力先

- 画面表示（イシュー詳細のリンク一覧セクション）
- JSON/GraphQL レスポンス

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ コントローラーでパラメータを受け取る
2. 認可チェック
   └─ 両方のイシューに対するadmin_issue_link権限を検証
3. 参照解析
   └─ issuable_referencesから対象イシューを抽出
4. バリデーション
   ├─ 自己参照チェック（同一イシューへのリンク不可）
   ├─ 重複チェック（既存リンクの確認）
   ├─ 親子関係チェック（親子関係とリンクは排他）
   └─ 最大リンク数チェック（100件まで）
5. リンク作成
   └─ issue_linksテーブルへのレコード挿入
6. システムノート作成
   └─ 両方のイシューに「related to」ノートを追加
7. イベント発行
   └─ GraphQL Subscriptions更新通知
8. レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B{操作種別}
    B -->|一覧取得| C[ListService実行]
    B -->|作成| D[CreateService実行]
    B -->|削除| E[DestroyService実行]

    D --> F{参照解析}
    F -->|成功| G{権限チェック}
    F -->|失敗| H[404 Not Found]

    G -->|許可| I{バリデーション}
    G -->|拒否| J[403 Forbidden]

    I -->|成功| K[リンク作成]
    I -->|自己参照| L[エラー: 自己参照不可]
    I -->|重複| M[409 Conflict]
    I -->|最大数超過| N[エラー: 上限超過]

    K --> O[システムノート作成]
    O --> P[GraphQL通知]
    P --> Q[成功レスポンス]

    E --> R{権限チェック}
    R -->|許可| S[リンク削除]
    R -->|拒否| J
    S --> T[終了]

    C --> U[関連イシュー取得]
    U --> V[シリアライズ]
    V --> W[レスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-001 | 自己参照禁止 | イシューは自分自身にリンクできない | 作成時 |
| BR-42-002 | 重複禁止 | 同じイシュー間に複数のリンクは作成できない（方向が逆でも不可） | 作成時 |
| BR-42-003 | 親子関係排他 | 親子関係（WorkItems::ParentLink）が存在するイシュー間にはリンクを作成できない | 作成時 |
| BR-42-004 | 最大リンク数制限 | 1つのイシューに対する最大リンク数は100件 | 作成時 |
| BR-42-005 | 双方向権限必要 | リンクの作成・削除には両方のイシューに対する権限が必要 | 作成・削除時 |
| BR-42-006 | 双方向ノート作成 | リンク作成時は両方のイシューにシステムノートが追加される | 作成時 |

### 計算ロジック

- リンク数カウント：source_idまたはtarget_idが当該イシューIDであるレコード数の合計

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 一覧取得 | issue_links | SELECT | イシューに関連するリンクを取得 |
| 作成 | issue_links | INSERT | 新規リンクレコードの挿入 |
| 作成 | notes | INSERT | システムノートの追加（両イシュー） |
| 削除 | issue_links | DELETE | リンクレコードの削除 |

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

#### issue_links

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | source_id | リンク元イシューのID | 必須 |
| INSERT | target_id | リンク先イシューのID | 必須 |
| INSERT | link_type | 0(relates_to)/1(blocks)/2(is_blocked_by) | enum値 |
| SELECT | source_id/target_id | WHERE source_id = ? OR target_id = ? | 関連イシュー取得 |
| DELETE | id | 指定されたリンクID | リンク削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 指定されたイシューまたはリンクが存在しない | イシュー/リンクの存在を確認 |
| 403 | Forbidden | 操作権限がない | 適切な権限を持つユーザーで操作 |
| 409 | Conflict | 既にリンクが存在する | 別のイシューを選択 |
| 422 | Validation Error | 自己参照、最大数超過、親子関係重複など | エラーメッセージに従い修正 |

### リトライ仕様

- 特別なリトライ処理は実装されていない
- 409 Conflictの場合は同一操作の再実行は不要

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

- リンク作成とシステムノート作成は個別のトランザクションで実行
- リンク削除は単一レコード操作のため暗黙のトランザクション

## パフォーマンス要件

- リンク一覧取得：500ms以内
- リンク作成：500ms以内
- QueryLimiting無効化（作成時）：大量の関連データ取得のため一時的にクエリ制限を解除

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

- **認証・認可**：すべての操作で認証必須、両イシューに対する権限チェック
- **クロスプロジェクトリンク**：異なるプロジェクト間でもリンク可能（権限があれば）
- **入力検証**：参照形式のパース時にReferenceExtractorで安全に解析

## 備考

- EE版では追加のリンクタイプ（blocks/is_blocked_by）が利用可能
- WorkItemsの関連リンク（WorkItems::RelatedWorkItemLink）と同じ基盤を共有

---

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

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

### 推奨読解順序

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

イシューリンクのデータモデルと関連concernを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | issue_link.rb | `app/models/issue_link.rb` | IssueLink モデルの定義、sourceとtargetの関連を理解 |
| 1-2 | issuable_link.rb | `app/models/concerns/issuable_link.rb` | 共通バリデーション、リンクタイプ定義、最大数制限(100件) |
| 1-3 | linkable_item.rb | `app/models/concerns/linkable_item.rb` | 親子関係チェックロジック |

**読解のコツ**:
- `belongs_to :source, class_name: 'Issue'`と`belongs_to :target, class_name: 'Issue'`で双方向の関連を定義
- `MAX_LINKS_COUNT = 100`（12行目）で最大リンク数を定義
- `check_self_relation`（49-55行目）で自己参照を防止
- `check_opposite_relation`（57-63行目）で逆方向の重複を防止

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

リクエストを受け付けるコントローラーの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | issue_links_controller.rb | `app/controllers/projects/issue_links_controller.rb` | リンク操作のエントリーポイント |

**主要処理フロー**:
1. **7行目**: before_action :authorize_admin_issue_link! - 作成・削除時の権限チェック
2. **8行目**: before_action :authorize_issue_link_association! - 削除時のリンク所属確認
3. **36-38行目**: list_service - 一覧取得サービスの呼び出し
4. **40-42行目**: create_service - 作成サービスの呼び出し
5. **44-46行目**: destroy_service - 削除サービスの呼び出し
6. **52-54行目**: create_params - 許可パラメータの定義（link_type, issuable_references）

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

ビジネスロジックを担当するサービスクラスを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create_service.rb (issuable_links) | `app/services/issuable_links/create_service.rb` | 共通の作成ロジック |
| 3-2 | create_service.rb (issue_links) | `app/services/issue_links/create_service.rb` | イシュー固有の作成ロジック |
| 3-3 | list_service.rb | `app/services/issue_links/list_service.rb` | 関連イシュー一覧取得 |
| 3-4 | destroy_service.rb | `app/services/issue_links/destroy_service.rb` | リンク削除ロジック |

**主要処理フロー**:
- **issuable_links/create_service.rb 15-40行目**: execute - エラーチェック、リンク作成、イベント追跡
- **issuable_links/create_service.rb 43-54行目**: relate_issuables - find_or_initialize_byで重複防止
- **issuable_links/create_service.rb 146-149行目**: create_notes - 双方向システムノート作成
- **issue_links/create_service.rb 12-14行目**: linkable_issuables - 権限フィルタリング

#### Step 4: 参照解析を理解する

イシュー参照の解析ロジックを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | reference_extractor.rb | `lib/gitlab/reference_extractor.rb` | 参照文字列からイシューを抽出 |

**主要処理フロー**:
- **issuable_links/create_service.rb 108-116行目**: extract_references - ReferenceExtractorで参照を解析

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

```
Projects::IssueLinksController
    │
    ├─ #index (IssuableLinks concern経由)
    │      └─ IssueLinks::ListService#execute
    │             └─ issuable.related_issues
    │                    └─ LinkedProjectIssueSerializer
    │
    ├─ #create (IssuableLinks concern経由)
    │      └─ IssueLinks::CreateService#execute
    │             ├─ Gitlab::ReferenceExtractor#analyze
    │             ├─ IssuableLinks::CreateService#relate_issuables
    │             │      ├─ IssueLink.find_or_initialize_by
    │             │      └─ link.save
    │             ├─ SystemNoteService.relate_issuable (x2)
    │             └─ GraphqlTriggers.work_item_updated
    │
    └─ #destroy (IssuableLinks concern経由)
           └─ IssueLinks::DestroyService#execute
                  ├─ permission_to_remove_relation?
                  ├─ link.destroy
                  └─ GraphqlTriggers.work_item_updated
```

### データフロー図

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

issuable_references ──────▶ ReferenceExtractor ──────────▶ Issue[]
(例: "#123 project#456")           │
                                   ▼
                           CreateService
                                   │
                           ┌───────┴───────┐
                           ▼               ▼
                    issue_links      notes (x2)
                    テーブル         テーブル
                           │
                           ▼
                    GraphQL通知 ──────────────────────────▶ UI更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| issue_link.rb | `app/models/issue_link.rb` | モデル | イシューリンクのデータモデル |
| issuable_link.rb | `app/models/concerns/issuable_link.rb` | Concern | 共通バリデーション・定数定義 |
| linkable_item.rb | `app/models/concerns/linkable_item.rb` | Concern | 親子関係チェック |
| issue_links_controller.rb | `app/controllers/projects/issue_links_controller.rb` | コントローラー | リンク操作のエントリーポイント |
| issuable_links.rb | `app/controllers/concerns/issuable_links.rb` | Concern | コントローラー共通処理 |
| create_service.rb | `app/services/issuable_links/create_service.rb` | サービス | 共通作成ロジック |
| create_service.rb | `app/services/issue_links/create_service.rb` | サービス | イシュー固有作成ロジック |
| list_service.rb | `app/services/issue_links/list_service.rb` | サービス | 一覧取得ロジック |
| destroy_service.rb | `app/services/issue_links/destroy_service.rb` | サービス | 削除ロジック |
| linked_project_issue_serializer.rb | `app/serializers/linked_project_issue_serializer.rb` | シリアライザ | 一覧のJSON出力 |
| reference_extractor.rb | `lib/gitlab/reference_extractor.rb` | ライブラリ | 参照文字列の解析 |
