# 画面設計書 147-ラベル編集

## 概要

本ドキュメントは、GitLabにおけるグループラベル編集画面の設計仕様を定義するものである。既存のグループラベルの属性を変更するための編集フォームを提供する。

### 本画面の処理概要

グループラベル編集画面は、既存のグループラベルを編集するためのフォーム画面である。ラベルのタイトル、説明、色、およびロック設定を変更できる。また、この画面からラベルの削除も可能。

**業務上の目的・背景**：運用開始後にラベルの名称や色を変更する必要が生じることがある。例えば、命名規則の統一や視認性向上のための色変更などに対応する。また、マージ後にラベルをロックすることで、意図しないラベル変更を防止できる。

**画面へのアクセス方法**：以下のいずれかの方法でアクセス可能である。
- グループラベル一覧画面の編集ボタンをクリック
- URL直接アクセス: `/groups/{group_path}/-/labels/{id}/edit`

**主要な操作・処理内容**：
1. ラベルタイトルの変更
2. ラベル説明の変更
3. ラベル色（背景色）の変更
4. マージ後ロック設定の変更（対応グループのみ）
5. ラベルの削除

**画面遷移**：
- 遷移元：グループラベル一覧画面
- 遷移先：元の画面（更新成功時）、同画面（バリデーションエラー時）、ラベル一覧（削除成功時）

**権限による表示制御**：
- Maintainer以上：ラベル編集・削除が可能（admin_label権限）
- 権限がない場合は404エラー

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 40 | ラベル管理 | 主機能 | グループラベルの編集・削除 |

## 画面種別

編集

## URL/ルーティング

- パス: `/groups/{group_path}/-/labels/{id}/edit`
- ルーティング: `groups/labels#edit`
- HTTPメソッド: GET（フォーム表示）、PATCH/PUT（更新処理）、DELETE（削除処理）
- 対応フォーマット: HTML

## 入出力項目

| 項目名 | 種別 | 必須 | データ型 | 説明 |
|--------|------|------|----------|------|
| id | パスパラメータ | 必須 | Integer | ラベルID |
| title | フォーム入力 | 必須 | String | ラベルタイトル（255文字以内） |
| description | フォーム入力 | 任意 | Text | ラベルの説明（512KB以内） |
| color | フォーム入力 | 任意 | String | 背景色（CSSカラーコード） |
| lock_on_merge | フォーム入力 | 任意 | Boolean | マージ後ロック設定 |
| archived | フォーム入力 | 任意 | Boolean | アーカイブ状態 |

## 表示項目

| 項目名 | データ型 | 説明 | 表示条件 |
|--------|----------|------|----------|
| ページヘッダー | Component | 「Edit label」タイトル | 常時 |
| アーカイブ警告 | Alert | アーカイブ済みの警告 | アーカイブ時かつlabels_archiveフラグ有効時 |
| アーカイブ解除ボタン | Button | アーカイブ解除 | アーカイブ時 |
| タイトル入力欄 | TextInput | ラベル名入力 | 常時 |
| 説明入力欄 | TextArea | ラベル説明入力 | 常時 |
| カラーピッカー | ColorPicker | 背景色選択 | 常時 |
| カラープリセット | ColorSwatches | 推奨色一覧 | 常時 |
| ロック設定 | Checkbox | マージ後ロック | supports_lock_on_merge時 |
| 保存ボタン | Button | 変更保存実行 | 常時 |
| キャンセルボタン | Button | 元画面へ戻る | 常時 |
| 削除ボタン | Button | ラベル削除 | 常時 |
| バリデーションエラー | Alert | 入力エラー表示 | エラー時 |

## イベント仕様

### 1-フォーム表示（GET edit）

1. `authorize_group_for_admin_labels!`による権限チェック（7行目）
2. `authorize_label_for_admin_label!`によるラベル権限チェック（8行目）
3. `label`メソッドで対象ラベル取得（95-97行目）
4. `save_previous_label_path`でリターンパス保存（124-126行目）
5. `supports_lock_on_merge?`でロック設定表示判定（ビュー5行目）
6. 共有フォームパーシャル（`shared/labels/_form`）をレンダリング

### 2-ラベル更新（PATCH/PUT update）

1. `authorize_group_for_admin_labels!`による権限チェック
2. `authorize_label_for_admin_label!`によるラベル権限チェック
3. `Labels::UpdateService`による更新処理（62行目）
4. バリデーション成功時：元画面へリダイレクト（65行目）
5. バリデーション失敗時：フォームを再表示（67行目）

### 3-ラベル削除（DELETE destroy）

1. `authorize_group_for_admin_labels!`による権限チェック
2. `authorize_label_for_admin_label!`によるラベル権限チェック
3. `@label.destroy`による削除実行（72行目）
4. 成功時：ラベル一覧へリダイレクト、削除メッセージ表示（73-74行目）
5. 失敗時：エラーメッセージ表示（76-77行目）

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| フォーム表示 | labels | SELECT | ラベル取得 |
| ラベル更新 | labels | UPDATE | ラベルレコード更新 |
| ラベル削除 | labels | DELETE | ラベルレコード削除 |
| ラベル削除 | label_links | DELETE | 関連リンク削除 |

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

#### labels

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | id=params[:id] | 編集対象取得 |
| UPDATE | title | 入力値 | 変更時 |
| UPDATE | description | 入力値 | 変更時 |
| UPDATE | color | 入力値（HEX変換後） | 変更時 |
| UPDATE | lock_on_merge | 入力値 | supports_lock_on_merge時 |
| UPDATE | archived | 入力値 | labels_archiveフラグ有効時 |
| UPDATE | updated_at | 現在時刻 | 自動設定 |
| DELETE | - | id=params[:id] | 削除時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG-001 | 成功 | {label_name} was removed | ラベル削除成功時 |
| MSG-002 | エラー | {error_messages} | 削除失敗時 |
| MSG-003 | エラー | Title can't be blank | タイトル未入力時 |
| MSG-004 | エラー | Title has already been taken | 同名ラベル存在時 |
| MSG-005 | 警告 | This label is archived and not available for use. | アーカイブ時 |

## 例外処理

| 例外条件 | 処理内容 | 遷移先 |
|----------|----------|--------|
| 管理権限なし | 404エラー | エラーページ |
| ラベル不存在 | 404エラー | エラーページ |
| ロックラベル削除 | エラーメッセージ表示 | ラベル一覧 |
| バリデーションエラー | エラーメッセージ表示 | 同画面 |

## 備考

- `lock_on_merge`がtrueのラベルは削除不可（prevent_locked_label_destroy）
- ロック設定は一度有効にすると、フォーム上で無効にできない（disabled属性）
- `supports_lock_on_merge?`はグループの設定に依存
- アーカイブされたラベルは使用不可だが、編集画面からアーカイブ解除可能
- `labels_archive`フィーチャーフラグでアーカイブ機能の有効/無効を制御
- リターンパスはセッションに保存され、元の画面に戻れる

---

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

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

### 推奨読解順序

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

ラベルモデルの構造、特にロック機能を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | label.rb | `app/models/label.rb` | ラベルモデル、lock_on_merge、prevent_locked_label_destroy |
| 1-2 | group_label.rb | `app/models/group_label.rb` | グループラベル固有の実装 |

**読解のコツ**: `before_destroy :prevent_locked_label_destroy`（28行目）でロックラベルの削除防止を確認。

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

コントローラーの`edit`、`update`、`destroy`アクションがエントリーポイント。

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

**主要処理フロー**:
1. **6-8行目**: before_actionでラベル取得と権限チェック
2. **9行目**: `save_previous_label_path`でリファラー保存
3. **57-59行目**: editアクションでリターンパス設定
4. **61-68行目**: updateアクションでLabels::UpdateService呼び出し
5. **71-79行目**: destroyアクションで削除処理

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit.html.haml | `app/views/groups/labels/edit.html.haml` | メインテンプレート |
| 3-2 | _form.html.haml | `app/views/shared/labels/_form.html.haml` | 共有フォームパーシャル |

**主要処理フロー**:
- **5行目**: `supports_lock_on_merge?`でロック設定表示判定
- **8行目**: 共有フォームパーシャルにshow_lock_on_merge渡す
- フォームパーシャル**5-12行目**: アーカイブ警告とアーカイブ解除ボタン
- フォームパーシャル**37-43行目**: ロック設定チェックボックス（disabled時は操作不可）
- フォームパーシャル**46-47行目**: 編集時は「Save changes」ボタン
- フォームパーシャル**53-56行目**: 削除ボタン

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

```
Groups::LabelsController#edit
    │
    ├─ label (before_action)
    │      └─ available_labels.find(params[:id])
    │
    ├─ authorize_group_for_admin_labels! (before_action)
    │
    ├─ authorize_label_for_admin_label! (before_action)
    │
    ├─ save_previous_label_path (before_action)
    │      └─ session[:previous_labels_path] = request.referer
    │
    └─ render 'shared/labels/form'
           └─ show_lock_on_merge: @group.supports_lock_on_merge?

Groups::LabelsController#update
    │
    ├─ label (before_action)
    │
    ├─ authorize_group_for_admin_labels! (before_action)
    │
    ├─ authorize_label_for_admin_label! (before_action)
    │
    └─ Labels::UpdateService.new(label_params).execute(@label)
           │
           └─ @label.update(params)
                  │
                  └─ labels table (UPDATE)

Groups::LabelsController#destroy
    │
    ├─ label (before_action)
    │
    ├─ authorize_group_for_admin_labels! (before_action)
    │
    ├─ authorize_label_for_admin_label! (before_action)
    │
    └─ @label.destroy
           │
           ├─ prevent_locked_label_destroy (before_destroy)
           │      └─ [lock_on_merge?] → throw(:abort)
           │
           └─ labels table (DELETE)
```

### データフロー図

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

Form params ─────────────▶ LabelsController#update ─────▶ Redirect/Render
(title, description,              │
 color, lock_on_merge)            │
                                  ▼
                          Labels::UpdateService
                                  │
                                  └── @label.update(params)
                                          │
                                          ├── validations
                                          │
                                          └── labels table (UPDATE)
                                                  │
                                                  ▼
                                          [成功] → redirect_back_or_group_labels_path
                                          [失敗] → render :edit

Delete request ──────────▶ LabelsController#destroy ───▶ Redirect
                                  │
                                  ├── prevent_locked_label_destroy
                                  │         │
                                  │         └── [locked?] → error
                                  │
                                  └── @label.destroy
                                          │
                                          └── labels table (DELETE)
                                                  │
                                                  ▼
                                          redirect_to group_labels_path
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| labels_controller.rb | `app/controllers/groups/labels_controller.rb` | コントローラー | edit/update/destroyアクション定義 |
| label.rb | `app/models/label.rb` | モデル | ラベル基底モデル、ロック機能 |
| group_label.rb | `app/models/group_label.rb` | モデル | グループラベル |
| edit.html.haml | `app/views/groups/labels/edit.html.haml` | ビュー | メインテンプレート |
| _form.html.haml | `app/views/shared/labels/_form.html.haml` | ビュー | 共有フォームパーシャル |
| update_service.rb | `app/services/labels/update_service.rb` | サービス | ラベル更新ロジック |
