# 機能設計書 13-エントリソート

## 概要

本ドキュメントは、Decap CMSにおけるエントリソート機能の設計を記述する。コレクション内のエントリを指定フィールドで昇降順ソートする処理の仕様を網羅する。

### 本機能の処理概要

エントリソート機能は、コレクション内のエントリ一覧を指定されたフィールドの値に基づいて昇順または降順に並び替える機能である。ソート設定はlocalStorageに永続化され、ブラウザ再起動後も維持される。

**業務上の目的・背景**：CMSで管理するコンテンツが増加すると、特定の条件（日付順、タイトル順等）でエントリを並び替えて目的のコンテンツを素早く見つけるニーズがある。ソート機能により、コンテンツ管理の効率を向上させる。

**機能の利用シーン**：コレクション一覧画面でソートコントロールを操作し、エントリの表示順を変更する。また、コレクション設定でデフォルトソートフィールドを指定することで、初回表示時のソート順も制御可能。

**主要な処理内容**：
1. `sortByField`: ソートリクエストをディスパッチし、全エントリを取得後にソート成功アクションをディスパッチ
2. リデューサーが`SORT_ENTRIES_REQUEST`で新しいソートキーをセットし、`SORT_ENTRIES_SUCCESS`でエントリ一覧を更新
3. `selectEntries`セレクターが`orderBy`（lodash）を使い、エントリ一覧をソートして返す
4. ソート設定はlocalStorageの`decap-cms.entries.sort`キーに永続化される
5. デフォルトソートフィールドの対応: `loadEntries`内で`selectDefaultSortField`をチェック

**関連システム・外部連携**：統合検索（Algolia等）のインテグレーションがある場合は、インテグレーションプロバイダ経由でエントリを取得する。

**権限による制御**：ソート操作自体に権限制御はない。閲覧権限のあるエントリのみがソート対象となる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | コレクション一覧画面 | 主画面 | ソートコントロールによるエントリ一覧の昇降順ソート。sortByFieldアクションを実行 |

## 機能種別

データ表示制御 - エントリ一覧の表示順序制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | Collection (Immutable.Map) | Yes | ソート対象のコレクション | - |
| key | string | Yes | ソートフィールドのキー名 | - |
| direction | SortDirection | No | ソート方向（デフォルト: Ascending） | SortDirection.Ascending / SortDirection.Descending / SortDirection.None |

### 入力データソース

コレクション一覧画面のソートコントロールUI。ユーザーの操作によりフィールド名とソート方向が指定される。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entries | EntryValue[] | ソート済みエントリ一覧 |
| sort | OrderedMap | コレクション別のソートキー・方向設定 |

### 出力先

- Redux Store（`entries.sort`、`entries.pages`）
- localStorage（`decap-cms.entries.sort`キー）

## 処理フロー

### 処理シーケンス

```
1. ユーザーがソートコントロールを操作
   └─ sortByFieldアクションをディスパッチ
2. SORT_ENTRIES_REQUESTをディスパッチ
   └─ ソートキーと方向をRedux状態に設定、isFetching=trueに
   └─ localStorageにソート設定を永続化
3. 既にフェッチ中の場合はソートキーの更新のみで終了
4. getAllEntriesで全エントリを取得
   └─ 統合検索インテグレーションまたはバックエンドからエントリ取得
5. SORT_ENTRIES_SUCCESSをディスパッチ
   └─ エントリ一覧をRedux状態に保存
6. selectEntriesセレクターがorderByでソート済みリストを返す
   └─ コンポーネントがソート済みリストを表示
```

### フローチャート

```mermaid
flowchart TD
    A[ユーザーがソート操作] --> B[sortByField dispatch]
    B --> C[SORT_ENTRIES_REQUEST dispatch]
    C --> D{isFetching?}
    D -->|Yes| E[ソートキー更新のみで終了]
    D -->|No| F[getAllEntries実行]
    F --> G{成功?}
    G -->|Yes| H[SORT_ENTRIES_SUCCESS dispatch]
    G -->|No| I[SORT_ENTRIES_FAILURE dispatch]
    H --> J[selectEntriesでソート済みリスト取得]
    J --> K[コンポーネント再描画]
    I --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-13-01 | デフォルトソート | コレクション設定にsortable_fieldsのdefaultがある場合、初回表示時にそのフィールドでソートする | loadEntries時 |
| BR-13-02 | ソート永続化 | ソート設定はlocalStorageに保存され、ブラウザ再起動後も維持される | sortByField時 |
| BR-13-03 | 全エントリ取得 | ソート実行時は全エントリを取得してクライアントサイドでソートする | sortByField時 |
| BR-13-04 | フェッチ中スキップ | 既にフェッチ中の場合はソートキーの更新のみ行い、エントリの再取得はスキップする | isFetching=true時 |
| BR-13-05 | OrderedMap管理 | 同一コレクションのソートキーはOrderedMapで管理され、一つのキーのみが有効 | 常時 |
| BR-13-06 | ソート方向 | Ascending（昇順）、Descending（降順）、None（ソートなし）の3つの方向をサポート | 常時 |

### 計算ロジック

- ソート: `orderBy(entries.toJS(), keys, orders)` - lodashのorderByを使用
- ソートデータパス: `selectSortDataPath(collection, key)` でフィールドのデータパスを解決
- デフォルトソート方向: `direction === 'desc' ? SortDirection.Descending : SortDirection.Ascending`

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

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

| 操作 | 対象ストレージ | 操作種別 | 概要 |
|-----|-------------|---------|------|
| sortByField | localStorage | UPDATE | ソート設定の永続化 |
| loadSort | localStorage | SELECT | ソート設定の読み込み（初回のみ） |

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

#### localStorage

| 操作 | キー | 値 | 備考 |
|-----|-----|-----|------|
| UPDATE | `decap-cms.entries.sort` | ソート設定のJSON（コレクション別キー・方向・インデックス） | persistSort関数で書き込み |
| SELECT | `decap-cms.entries.sort` | ソート設定のJSON | loadSort関数で読み込み（once） |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SORT_ENTRIES_FAILURE | ソートエラー | getAllEntriesの取得に失敗した場合 | ソートキーを削除し、isFetching=falseに設定。localStorageのソート設定も更新 |

### リトライ仕様

自動リトライは行わない。ユーザーが再度ソート操作を行うことでリトライとなる。

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

Redux状態の更新とlocalStorageの更新は別々に行われるが、同期的に処理されるため整合性は保たれる。

## パフォーマンス要件

- 全エントリの取得が必要となるため、大量のエントリを持つコレクションではパフォーマンスに影響する可能性がある
- ソートはクライアントサイドのlodash `orderBy`で行われるため、JavaScriptの処理能力に依存
- ソート設定のlocalStorage永続化は同期処理だが、JSONのサイズは小さいため影響は軽微

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

- ソート設定はlocalStorageに保存されるが、機密情報は含まれない
- ソート操作によるバックエンドAPIへのアクセスは通常の認証が必要

## 備考

- `loadSort`関数は`once`（lodash）でラップされており、初回のみlocalStorageから読み込まれる
- ネストコレクションやi18n対応コレクションでは`listAllEntries`が使用されるため、ソートの有無に関わらず全エントリが取得される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | redux.ts | `packages/decap-cms-core/src/types/redux.ts` | SortDirection enum、SortMap、SortObject型の定義を確認 |
| 1-2 | entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | storageSortKey定数（79行目）、StorageSortObject型（89行目）、loadSort関数（92-112行目）を確認 |

**読解のコツ**: ソート状態はOrderedMapで管理されている。OrderedMapはキーの挿入順序を保持するImmutable.jsのデータ構造で、複数のソートキーの優先順位を表現する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | sortByField関数（171-215行目）が主要エントリーポイント |

**主要処理フロー**:
1. **176-177行目**: getState()で現在の状態を取得
2. **179行目**: selectIsFetchingでフェッチ中かチェック
3. **180-187行目**: SORT_ENTRIES_REQUESTをディスパッチ
4. **188-190行目**: フェッチ中なら早期リターン
5. **193行目**: getAllEntriesで全エントリ取得
6. **194-202行目**: SORT_ENTRIES_SUCCESSをディスパッチ

#### Step 3: リデューサーの状態管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | SORT_ENTRIES_REQUESTケース（252-263行目）、SORT_ENTRIES_SUCCESSケース（265-289行目）、SORT_ENTRIES_FAILUREケース（291-300行目） |

**主要処理フロー**:
- **252-263行目**: OrderedMap({ [key]: Map({ key, direction }) })でソート状態を設定、persistSortでlocalStorageに永続化
- **265-289行目**: エントリ一覧をRedux状態に保存（FILTER/GROUP_SUCCESSと共有のケース）
- **291-300行目**: ソートキーを削除、localStorageを更新

#### Step 4: セレクターによるソート適用を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | selectEntries関数（426-458行目）でのorderBy適用 |

**主要処理フロー**:
- **430-437行目**: selectEntriesSortFieldsでソートフィールドを取得し、lodashのorderByで並び替え

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

```
CollectionTopコンポーネント（ソートコントロールUI）
    │
    ├─ sortByField(collection, key, direction)  [actions/entries.ts:171-215]
    │      ├─ dispatch(SORT_ENTRIES_REQUEST)
    │      ├─ getAllEntries(state, collection)  [actions/entries.ts:161-169]
    │      │      └─ provider.listAllEntries(collection)
    │      └─ dispatch(SORT_ENTRIES_SUCCESS) or dispatch(SORT_ENTRIES_FAILURE)
    │
    └─ loadEntries(collection)  [actions/entries.ts:575-658]
           ├─ selectEntriesSortFields  → 既存ソート設定があればsortByFieldへ
           └─ selectDefaultSortField  → デフォルトソート設定があればsortByFieldへ

リデューサー:
entries (reducer)  [reducers/entries.ts:159-362]
    ├─ SORT_ENTRIES_REQUEST  [252-263行目]
    │      └─ persistSort(sort)  [118-135行目]
    ├─ SORT_ENTRIES_SUCCESS  [265-289行目]
    └─ SORT_ENTRIES_FAILURE  [291-300行目]

セレクター:
selectEntries  [reducers/entries.ts:426-458]
    └─ orderBy(entries.toJS(), keys, orders)
```

### データフロー図

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

ユーザー操作          ───▶  sortByField                ───▶  Redux entries.sort
(フィールド名, 方向)         ├─ SORT_ENTRIES_REQUEST            ├─ localStorage永続化
                            ├─ getAllEntries                   └─ エントリ一覧更新
                            └─ SORT_ENTRIES_SUCCESS
                                    │
                                    ▼
                            selectEntries              ───▶  ソート済みエントリ一覧
                            └─ lodash.orderBy                 (UIコンポーネントへ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | sortByField、getAllEntriesアクション |
| entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | ソース | ソート状態管理、selectEntries/selectEntriesSortFieldsセレクター |
| collections.ts (reducer) | `packages/decap-cms-core/src/reducers/collections.ts` | ソース | selectDefaultSortField、selectSortDataPathセレクター |
