# 機能設計書 14-エントリフィルタ

## 概要

本ドキュメントは、Decap CMSにおけるエントリフィルタ機能の設計を記述する。コレクション内のエントリをview_filters設定に基づいてフィルタリングする処理の仕様を網羅する。

### 本機能の処理概要

エントリフィルタ機能は、コレクション設定の`view_filters`で定義されたフィルタ条件に基づき、エントリ一覧の表示を絞り込む機能である。フィルタは正規表現パターンマッチングにより実行される。

**業務上の目的・背景**：大量のコンテンツを管理するCMSでは、特定の条件に合致するエントリのみを表示するフィルタ機能が不可欠である。下書き状態のエントリのみ、特定カテゴリのエントリのみ等の条件で絞り込むことで、コンテンツ管理の効率を向上させる。

**機能の利用シーン**：コレクション一覧画面でフィルターコントロールを操作し、view_filtersに定義された条件をトグルで有効/無効にする。複数のフィルタを同時に有効にすることも可能（AND条件）。

**主要な処理内容**：
1. `filterByField`: フィルタリクエストをディスパッチし、全エントリを取得後にフィルタ成功アクションをディスパッチ
2. リデューサーが`FILTER_ENTRIES_REQUEST`でフィルタのactive状態をトグル
3. `selectEntries`セレクターが正規表現パターンマッチングでエントリをフィルタリング
4. 複数フィルタが有効な場合はAND条件で全てのフィルタに合致するエントリのみ表示

**関連システム・外部連携**：ソート機能（No.13）と連携して動作する。ソートとフィルタは同時に適用可能。

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

## 関連画面

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

## 機能種別

データ表示制御 - エントリ一覧の表示フィルタリング

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collection | Collection (Immutable.Map) | Yes | フィルタ対象のコレクション | - |
| filter | ViewFilter | Yes | フィルタ定義（id, label, field, pattern） | - |

### ViewFilter型の構造

| プロパティ | 型 | 説明 |
|-----------|-----|------|
| id | string | フィルタの一意識別子 |
| label | string | フィルタの表示ラベル |
| field | string | フィルタ対象のフィールド名 |
| pattern | string | 正規表現パターン |

### 入力データソース

コレクション設定ファイル（config.yml）の`view_filters`定義。UIのフィルターコントロールからユーザーが選択する。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entries | EntryValue[] | フィルタ済みエントリ一覧 |
| filter | FilterMap | コレクション別のフィルタ設定（active状態を含む） |

### 出力先

Redux Store（`entries.filter`、`entries.pages`）

## 処理フロー

### 処理シーケンス

```
1. ユーザーがフィルターコントロールをクリック
   └─ filterByFieldアクションをディスパッチ
2. FILTER_ENTRIES_REQUESTをディスパッチ
   └─ フィルタのactive状態をトグル（有効⇔無効）
3. 既にフェッチ中の場合はフィルタ状態の更新のみで終了
4. getAllEntriesで全エントリを取得
5. FILTER_ENTRIES_SUCCESSをディスパッチ
   └─ エントリ一覧をRedux状態に保存
6. selectEntriesセレクターが正規表現パターンマッチングでフィルタ
   └─ コンポーネントがフィルタ済みリストを表示
```

### フローチャート

```mermaid
flowchart TD
    A[ユーザーがフィルタ操作] --> B[filterByField dispatch]
    B --> C[FILTER_ENTRIES_REQUEST dispatch]
    C --> D[フィルタactive状態トグル]
    D --> E{isFetching?}
    E -->|Yes| F[フィルタ状態更新のみで終了]
    E -->|No| G[getAllEntries実行]
    G --> H{成功?}
    H -->|Yes| I[FILTER_ENTRIES_SUCCESS dispatch]
    H -->|No| J[FILTER_ENTRIES_FAILURE dispatch]
    I --> K[selectEntriesでフィルタ適用]
    K --> L{全フィルタにマッチ?}
    L -->|Yes| M[エントリを表示]
    L -->|No| N[エントリを非表示]
    J --> O[フィルタ削除・エラー処理]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-14-01 | トグル動作 | フィルタのactive状態はクリックごとにトグル（有効/無効）される | FILTER_ENTRIES_REQUEST時 |
| BR-14-02 | AND条件 | 複数のフィルタが有効な場合、全てに合致するエントリのみ表示 | selectEntries時 |
| BR-14-03 | 正規表現マッチ | フィルタパターンはRegExpで評価され、フィールド値のString表現に対してtestされる | selectEntries時 |
| BR-14-04 | 全エントリ取得 | フィルタ実行時は全エントリを取得してクライアントサイドでフィルタする | filterByField時 |
| BR-14-05 | フェッチ中スキップ | 既にフェッチ中の場合はフィルタ状態の更新のみ行い、エントリの再取得はスキップ | isFetching=true時 |
| BR-14-06 | undefined除外 | フィールド値がundefinedの場合はフィルタにマッチしない | selectEntries時 |

### 計算ロジック

- フィルタマッチング: `new RegExp(String(pattern)).test(String(toMatch))` - パターンを正規表現としてコンパイルし、フィールド値の文字列表現に対してテスト
- フィールドパスの解決: `keyToPathArray(field)` でドット区切りのフィールドパスを配列に変換し、`data.getIn()`でネストされたデータにアクセス

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

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

本機能ではデータベース操作は発生しない。Redux Storeへの状態更新のみである。

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

該当なし（クライアントサイド操作のみ）。ソート機能と異なり、フィルタ設定のlocalStorage永続化は行われない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| FILTER_ENTRIES_FAILURE | フィルタエラー | getAllEntriesの取得に失敗した場合 | フィルタ設定を削除し、isFetching=falseに設定 |

### リトライ仕様

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

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

Redux状態の更新は同期的に処理される。フィルタ状態のトグルとエントリ一覧の更新は別々のアクションで行われるが、Redux middlewareにより順序が保証される。

## パフォーマンス要件

- フィルタ実行時は全エントリの取得が必要となるため、大量エントリがある場合に注意
- 正規表現パターンの評価はクライアントサイドで行われるため、大量エントリと複雑な正規表現の組み合わせでは負荷が大きい可能性がある
- selectEntriesセレクターはコンポーネントの再描画ごとに呼ばれるため、メモ化を検討する余地がある

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

- フィルタパターンは正規表現として評価されるため、ReDoS（正規表現サービス拒否）攻撃のリスクがある。ただし、パターンはconfig.ymlで管理者が定義するため、実質的なリスクは低い。

## 備考

- フィルタ設定はlocalStorageに永続化されない（ソートとの違い）
- ネストされたフィールドへのアクセスには`keyToPathArray`（stringTemplate）を使用

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | redux.ts | `packages/decap-cms-core/src/types/redux.ts` | ViewFilter型、FilterMap型の定義を確認 |

**読解のコツ**: ViewFilterはid, label, field, patternの4プロパティを持つ。FilterMapはImmutable化された状態で、activeプロパティが動的に追加される。

#### Step 2: アクションクリエータを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | filterByField関数（217-254行目） |

**主要処理フロー**:
1. **218-219行目**: getState()で状態取得
2. **221行目**: selectIsFetchingでフェッチ中かチェック
3. **222-228行目**: FILTER_ENTRIES_REQUESTをディスパッチ
4. **229-231行目**: フェッチ中なら早期リターン
5. **234行目**: getAllEntriesで全エントリ取得
6. **235-242行目**: FILTER_ENTRIES_SUCCESSをディスパッチ

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | entries.ts (reducer) | `packages/decap-cms-core/src/reducers/entries.ts` | FILTER_ENTRIES_REQUESTケース（302-313行目）、FILTER_ENTRIES_FAILUREケース（315-323行目） |

**主要処理フロー**:
- **302-313行目**: `FILTER_ENTRIES_REQUEST` - 現在のフィルタを取得（なければ新規作成）し、activeプロパティをトグル
- **315-323行目**: `FILTER_ENTRIES_FAILURE` - フィルタを削除しisFetching=false

#### Step 4: セレクターによるフィルタ適用を理解する

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

**主要処理フロー**:
- **439-455行目**: selectEntriesFilterFieldsでactiveフィルタを取得し、everyでAND条件マッチング
- **449行目**: `new RegExp(String(pattern)).test(String(toMatch))` で正規表現マッチ

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

```
CollectionTopコンポーネント（フィルターコントロールUI）
    │
    └─ filterByField(collection, filter)  [actions/entries.ts:217-254]
           ├─ dispatch(FILTER_ENTRIES_REQUEST)
           │      └─ entries reducer [302-313行目]
           │             └─ filter.active トグル
           ├─ getAllEntries(state, collection)  [actions/entries.ts:161-169]
           │      └─ provider.listAllEntries(collection)
           └─ dispatch(FILTER_ENTRIES_SUCCESS) or dispatch(FILTER_ENTRIES_FAILURE)

セレクター:
selectEntries  [reducers/entries.ts:426-458]
    ├─ selectEntriesFilterFields  [396-404行目]
    └─ entries.filter(e => filters.every(f => RegExp.test(...)))
```

### データフロー図

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

ViewFilter              ───▶  filterByField             ───▶  Redux entries.filter
(field, pattern)               ├─ FILTER_ENTRIES_REQUEST       ├─ active状態トグル
                               ├─ getAllEntries                └─ エントリ一覧更新
                               └─ FILTER_ENTRIES_SUCCESS
                                       │
                                       ▼
                               selectEntries             ───▶  フィルタ済みエントリ一覧
                               └─ RegExp(pattern).test(value)  (UIコンポーネントへ)
```

### 関連ファイル一覧

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