# 画面設計書 145-ラベル一覧

## 概要

本ドキュメントは、GitLabにおけるグループラベル一覧画面の設計仕様を定義するものである。グループレベルで定義されたラベルの一覧表示、検索、管理を行う画面を提供する。

### 本画面の処理概要

グループラベル一覧画面は、グループに定義されたラベルを一覧表示し、管理するための画面である。ラベルは課題やマージリクエストの分類に使用され、グループレベルで定義されたラベルは配下のすべてのプロジェクトで利用可能となる。

**業務上の目的・背景**：プロジェクト横断的な課題管理を効率化するため、共通のラベルをグループレベルで定義する必要がある。例えば、「Bug」「Enhancement」「Priority::High」などのラベルをグループ全体で統一することで、複数プロジェクトにまたがる課題の分類・フィルタリングが容易になる。継承ラベル（親グループから）の表示により、ラベル体系全体を把握できる。

**画面へのアクセス方法**：以下のいずれかの方法でアクセス可能である。
- グループ詳細画面のサイドバーから「Manage > Labels」を選択
- URL直接アクセス: `/groups/{group_path}/-/labels`
- グループ設定メニューからアクセス

**主要な操作・処理内容**：
1. グループラベルの一覧表示（ページネーション付き）
2. ラベルの検索（名前・説明でフィルタリング）
3. 購読中ラベルのフィルタリング
4. アーカイブ済みラベルのフィルタリング
5. ラベル新規作成画面への遷移
6. ラベル編集・削除
7. ラベルの購読/購読解除

**画面遷移**：
- 遷移元：グループ詳細画面、グループサイドバー
- 遷移先：ラベル新規作成画面、ラベル編集画面

**権限による表示制御**：
- Guest：ラベル一覧の閲覧（read_label権限）
- Developer以上：ラベルの購読
- Maintainer以上：ラベル作成、編集、削除（admin_label権限）

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 40 | ラベル管理 | 主機能 | グループラベルの一覧表示 |

## 画面種別

一覧

## URL/ルーティング

- パス: `/groups/{group_path}/-/labels`
- ルーティング: `groups/labels#index`
- HTTPメソッド: GET
- 対応フォーマット: HTML, JSON

## 入出力項目

| 項目名 | 種別 | 必須 | データ型 | 説明 |
|--------|------|------|----------|------|
| page | クエリパラメータ | 任意 | Integer | ページ番号 |
| search | クエリパラメータ | 任意 | String | ラベル検索クエリ |
| subscribed | クエリパラメータ | 任意 | String | 購読中のみ表示 |
| archived | クエリパラメータ | 任意 | Boolean | アーカイブ済みのみ表示 |
| sort | クエリパラメータ | 任意 | String | ソート順（name_asc等） |
| include_descendant_groups | クエリパラメータ | 任意 | Boolean | 子孫グループのラベル含む |

## 表示項目

| 項目名 | データ型 | 説明 | 表示条件 |
|--------|----------|------|----------|
| ナビゲーションタブ | Tab | フィルタリングタブ | labels_archiveフラグ有効時 |
| ラベル一覧 | CrudComponent | ラベルリスト | ラベル存在時 |
| ラベル色 | Color | ラベルの背景色 | 常時 |
| ラベル名 | String | ラベルのタイトル | 常時 |
| ラベル説明 | Text | ラベルの説明文 | 設定時 |
| 課題数 | Integer | ラベルが付与された課題数 | 常時 |
| MR数 | Integer | ラベルが付与されたMR数 | 常時 |
| 購読ボタン | Button | 購読/購読解除 | ログインユーザー |
| 編集ボタン | Button | ラベル編集へ遷移 | admin_label権限 |
| 削除ボタン | Button | ラベル削除 | admin_label権限 |
| 新規作成ボタン | Button | ラベル作成へ遷移 | admin_label権限 |
| 検索結果なしメッセージ | Text | 該当ラベルなし | 検索結果0件時 |
| 空状態 | EmptyState | 初期状態 | ラベル0件時 |

## イベント仕様

### 1-ラベル一覧表示（GET）

1. `authorize_read_labels!`による閲覧権限チェック（暗黙的）
2. `LabelsFinder`によるラベル取得
3. `only_group_labels: true`でグループラベルのみ取得
4. `include_ancestor_groups: true`で継承ラベルも取得
5. 検索・フィルタの適用
6. `LabelsPreloader`によるプリロード
7. ページネーション適用
8. CrudComponentによる一覧表示

### 2-ラベル検索

1. searchパラメータの取得
2. `LabelsFinder`に検索条件を渡す
3. タイトル・説明でのあいまい検索
4. 検索結果をページネーション付きで表示

### 3-購読/購読解除（POST toggle_subscription）

1. ToggleSubscriptionAction concernによる処理
2. `subscribable_resource`メソッドでラベル取得
3. 購読状態の切り替え
4. JSON形式でレスポンス

### 4-ラベル削除（DELETE）

1. `authorize_label_for_admin_label!`による権限チェック
2. `@label.destroy`による削除実行
3. ロックされたラベルは削除不可
4. 成功時：ラベル一覧へリダイレクト、削除メッセージ表示

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 一覧表示 | labels | SELECT | グループラベル取得 |
| 一覧表示 | label_links | SELECT | 関連カウント取得 |
| 購読切替 | subscriptions | INSERT/DELETE | 購読状態更新 |
| ラベル削除 | labels | DELETE | ラベルレコード削除 |
| ラベル削除 | label_links | DELETE | 関連リンク削除 |

### テーブル別更新項目詳細

#### labels

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, title, description, color | group_id=グループID | グループラベル |
| SELECT | archived | フィルタ条件 | アーカイブ状態 |
| DELETE | - | label_id | 削除時 |

#### subscriptions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | subscribable_type | 'Label' | 購読時 |
| INSERT | subscribable_id | ラベルID | 購読時 |
| INSERT | user_id | current_user.id | 購読時 |
| DELETE | - | 上記条件 | 購読解除時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | 成功 | {label_name} was removed | ラベル削除成功時 |
| MSG-002 | エラー | {error_messages} | 削除失敗時 |
| MSG-003 | 情報 | No labels with such name or description | 検索結果0件時 |
| MSG-004 | 情報 | You do not have any subscriptions yet | 購読フィルタ結果0件時 |
| MSG-005 | 情報 | No archived labels | アーカイブフィルタ結果0件時 |

## 例外処理

| 例外条件 | 処理内容 | 遷移先 |
|----------|----------|--------|
| 閲覧権限なし | 404エラー | エラーページ |
| ラベル不存在 | 404エラー | エラーページ |
| ロックラベル削除 | エラーメッセージ表示 | ラベル一覧 |

## 備考

- labels_archiveフィーチャーフラグでアーカイブ機能の有効/無効を制御
- ラベルはデフォルトでタイトル昇順でソート（default_scope）
- グループラベルは配下の全プロジェクトで利用可能
- 親グループのラベルも継承して表示される（include_ancestor_groups）
- ラベルの色はCSS colorコードで指定
- lock_on_mergeが有効なラベルは削除不可

---

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

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

### 推奨読解順序

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

ラベルモデルとグループとの関連を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | label.rb | `app/models/label.rb` | ラベルモデル、バリデーション、スコープ |
| 1-2 | group_label.rb | `app/models/group_label.rb` | グループラベル固有の実装 |
| 1-3 | group.rb | `app/models/group.rb` | has_many :labels関連 |

**読解のコツ**: `default_scope { order(title: :asc) }`によりデフォルトでタイトルソート。`lock_on_merge`属性に注目。

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

コントローラーの`index`アクションがエントリーポイント。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | labels_controller.rb | `app/controllers/groups/labels_controller.rb` | indexアクション |

**主要処理フロー**:
1. **20-31行目**: indexアクションの定義
2. **25行目**: `available_labels`で`LabelsFinder`呼び出し
3. **26行目**: `LabelsPreloader`でプリロード
4. **128-144行目**: `available_labels`メソッドでフィルタ条件設定

#### Step 3: ファインダーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | labels_finder.rb | `app/finders/labels_finder.rb` | ラベル検索ロジック |

#### Step 4: ビューレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.html.haml | `app/views/groups/labels/index.html.haml` | メインテンプレート |
| 4-2 | _label.html.haml | `app/views/shared/_label.html.haml` | ラベル表示パーシャル |
| 4-3 | _nav.html.haml | `app/views/shared/labels/_nav.html.haml` | ナビゲーション |

**主要処理フロー**:
- **11-12行目**: labels_archiveフラグによるナビ表示制御
- **24行目**: CrudComponentでラベル一覧表示
- **27行目**: ラベルパーシャルの繰り返し描画
- **40行目**: 空状態の表示

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

```
Groups::LabelsController#index
    │
    ├─ authorize_read_labels! (暗黙的)
    │
    ├─ push_frontend_feature_flag(:labels_archive)
    │
    └─ available_labels (private method)
           │
           └─ LabelsFinder.new(
                  current_user,
                  group_id: @group.id,
                  only_group_labels: true,
                  include_ancestor_groups: true,
                  sort: sort,
                  subscribed: params[:subscribed],
                  search: params[:search],
                  archived: params[:archived]
              ).execute
                  │
                  ├─ labels table (SELECT)
                  │
                  └─ Preloaders::LabelsPreloader
                         └─ プリロード実行
```

### データフロー図

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

Request params ──────────▶ LabelsController#index ──────▶ HTML Response
(page, search,                   │                        (ラベル一覧)
 subscribed, archived)           │
                                 ▼
                         LabelsFinder
                                 │
                                 ├── params validation
                                 │
                                 └── labels table (SELECT)
                                         │
                                         ├── group_id filter
                                         ├── include_ancestor_groups
                                         ├── search filter
                                         ├── subscribed filter
                                         └── archived filter
                                                │
                                                ▼
                                         Pagination
                                                │
                                                ▼
                                         CrudComponent
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| labels_controller.rb | `app/controllers/groups/labels_controller.rb` | コントローラー | indexアクション定義 |
| label.rb | `app/models/label.rb` | モデル | ラベル基底モデル |
| group_label.rb | `app/models/group_label.rb` | モデル | グループラベル |
| index.html.haml | `app/views/groups/labels/index.html.haml` | ビュー | メインテンプレート |
| _label.html.haml | `app/views/shared/_label.html.haml` | ビュー | ラベル表示パーシャル |
| _nav.html.haml | `app/views/shared/labels/_nav.html.haml` | ビュー | ナビゲーション |
| labels_finder.rb | `app/finders/labels_finder.rb` | ファインダー | ラベル検索 |
| labels_preloader.rb | `app/models/preloaders/labels_preloader.rb` | プリローダー | 関連データプリロード |
| crud_component.rb | `app/components/layouts/crud_component.rb` | コンポーネント | 一覧UI |
