# 画面設計書 20-メンバーアクティビティ画面

## 概要

本ドキュメントは、Ghost管理画面におけるメンバーアクティビティ画面の設計仕様を定義するものである。

### 本画面の処理概要

メンバーアクティビティ画面は、サイト上でのメンバーの活動履歴を一覧表示する画面である。サインアップ、メール開封、投稿閲覧、コメント、サブスクリプション変更など様々なイベントタイプを時系列で表示できる。特定のメンバーに絞り込んだ表示も可能。

**業務上の目的・背景**：メンバーの行動を把握し、エンゲージメント分析やトラブルシューティングに活用する。メンバー詳細画面から特定メンバーの全アクティビティを確認する際にも使用される。

**画面へのアクセス方法**：サイドバーの「Activity」メニューをクリック、メンバー詳細画面の「View all member activity」リンクをクリック、またはURL `/ghost/#/members-activity` で直接アクセス可能。

**主要な操作・処理内容**：
1. アクティビティの時系列表示
2. イベントタイプによるフィルタリング
3. 特定メンバーによるフィルタリング
4. 無限スクロールによる追加ロード
5. イベント詳細の表示（関連投稿、メール等へのリンク）

**画面遷移**：
- 遷移元: サイドバーメニュー、メンバー詳細画面
- 遷移先: 投稿詳細画面、メンバー詳細画面、メールプレビュー

**権限による表示制御**：
- Author/Contributor: 本画面にアクセス不可（ホームへリダイレクト）
- Editor/Administrator/Owner: 全機能利用可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|-------------------|
| 4 | メンバー管理 | 主機能 | メンバーアクティビティの表示 |

## 画面種別

一覧

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| URL | `/ghost/#/members-activity` |
| URL（メンバー指定） | `/ghost/#/members-activity?member={member_id}` |
| ルート名 | members-activity |
| ルートファイル | `ghost/admin/app/routes/members-activity.js` |
| コントローラファイル | `ghost/admin/app/controllers/members-activity.js` |
| テンプレートファイル | `ghost/admin/app/templates/members-activity.hbs` |

### クエリパラメータ

| パラメータ名 | 型 | デフォルト値 | 説明 |
|-------------|-----|-------------|------|
| excludedEvents | string | null | 除外するイベントタイプ（カンマ区切り） |
| member | string | null | フィルター対象のメンバーID |

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 必須 | 型 | 説明 |
|--------|--------|------|-----|------|
| イベントタイプフィルター | excludedEvents | - | string | 除外イベントタイプ |
| メンバーフィルター | member | - | string | メンバーID |

## 表示項目

### ヘッダー部

| 項目名 | 説明 |
|--------|------|
| パンくずリスト | メンバー指定時のみ表示（Member activity > メンバー名） |
| タイトル | 「Member activity」（メンバー未指定時） |
| イベントタイプフィルター | EventTypeFilterコンポーネント |
| メンバーフィルター | MemberFilterコンポーネント |

### アクティビティテーブル

| 項目名 | フィールド | 説明 |
|--------|-----------|------|
| イベントアイコン | event.icon | イベントタイプに応じたアイコン |
| アクション | event.action | イベントの説明文 |
| 詳細情報 | event.info | 追加情報（括弧内） |
| 関連オブジェクト | event.object | 関連する投稿、メール等 |
| URL/リンク | event.url/route | 関連リソースへのリンク |
| 説明 | event.description | アトリビューション情報等 |
| 時刻 | event.timestamp | 「X ago」形式の相対時刻 |

### メンバー指定時追加表示

| 項目名 | 説明 |
|--------|------|
| メンバー詳細 | GhMemberDetailsActivityコンポーネント |

### 空状態表示

| 条件 | 表示内容 |
|------|---------|
| イベントなし | MembersActivity::NoEventsコンポーネント |

## イベント仕様

### 1-イベントタイプフィルター変更（changeExcludedEvents）

- 処理: excludedEventsクエリパラメータを更新し、再フェッチ
- router.transitionTo({queryParams: {excludedEvents: newList}})

### 2-メンバーフィルター変更（changeMember）

- 処理: memberクエリパラメータを更新し、再フェッチ
- router.transitionTo({queryParams: {member: member?.id}})

### 3-無限スクロール（loadNextPage）

- 処理: GhScrollTriggerでスクロール位置を監視
- triggerOffset=250で追加ロード開始
- cursor（最後のイベントのcreated_at）で次ページを取得

### 4-イベントフェッチ（loadEventsTask）

- 処理: members-event-fetcherでAPIを呼び出し
- API: `GET /members/events?filter={filter}&limit={pageSize}`
- pageSize: 通常50件、メンバー詳細での表示は5件

### 5-関連リソースへの遷移

- 処理: イベントに応じてLinkToまたは外部リンク
- route/modelがある場合: LinkTo
- urlがある場合: 外部リンク（target=_blank）
- emailがある場合: GhEmailPreviewLink

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| アクティビティ表示 | members_events | SELECT | イベントデータの取得 |

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

本画面は参照のみで、データベースの更新は行わない。

## メッセージ仕様

| メッセージID | 種別 | 条件 | メッセージ内容 |
|-------------|------|------|---------------|
| MSG-01 | 表示 | 空状態（メンバー詳細） | All events related to this member will be shown here. |
| MSG-02 | 表示 | 空状態（全体） | NoEventsコンポーネントで表示 |

## 例外処理

| 例外ケース | 処理内容 |
|-----------|---------|
| 権限不足 | ホーム画面へリダイレクト |
| API通信エラー | isError=true、errorMessage表示 |

## 備考

- aggregated_click_eventは常に除外される
- メンバー未指定時はEMAIL_EVENTSも除外（APIページネーション制限のため）
- ニュースレター無効時はEMAIL_EVENTSとNEWSLETTER_EVENTSが除外
- コメント無効時はcomment_eventが除外
- parse-member-eventヘルパーでイベントデータを表示用に変換
- hasMultipleNewslettersでニュースレター名の表示を制御

---

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

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

### 推奨読解順序

#### Step 1: ルートを理解する

シンプルなルート構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | members-activity.js | `ghost/admin/app/routes/members-activity.js` | 権限チェック継承、titleToken |

**主要処理フロー**:
- **3行目**: MembersManagementRoute継承
- **4-8行目**: buildRouteInfoMetadata - titleToken='Activity'

#### Step 2: コントローラーを理解する

フィルター制御のロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | members-activity.js | `ghost/admin/app/controllers/members-activity.js` | queryParams、hiddenEvents |

**主要処理フロー**:
- **15-18行目**: queryParams定義（excludedEvents, member）
- **20行目**: MemberFetcherでメンバーレコード取得
- **25-38行目**: hiddenEvents computed - 状況に応じた除外イベント
- **40-42行目**: fullExcludedEvents - 指定+hidden
- **44-52行目**: changeExcludedEvents、changeMember - フィルター変更

#### Step 3: テンプレートを理解する

UI構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | members-activity.hbs | `ghost/admin/app/templates/members-activity.hbs` | ヘッダー、フィルター、テーブル |

**主要処理フロー**:
- **3-16行目**: ヘッダー（メンバー指定時のパンくずリスト）
- **18-27行目**: フィルターコンポーネント
- **30-51行目**: members-event-fetcherでイベント取得、テーブル表示
- **38-40行目**: GhScrollTriggerで無限スクロール

#### Step 4: イベントフェッチャーを理解する

データ取得ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | members-event-fetcher.js | `ghost/admin/app/helpers/members-event-fetcher.js` | イベント取得、ページネーション |

**主要処理フロー**:
- **9-38行目**: MembersEventsFetcher Resource定義
- **40-58行目**: setup() - 初期ロード
- **60-85行目**: loadNextPage() - カーソルベースページネーション
- **103-130行目**: loadEventsTask - API呼び出し

#### Step 5: イベントフィルターを理解する

フィルター生成ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | members-event-filter.js | `ghost/admin/app/helpers/members-event-filter.js` | フィルター文字列生成 |

**主要処理フロー**:
- **6-7行目**: EMAIL_EVENTS、NEWSLETTER_EVENTS定数
- **14-56行目**: compute() - フィルター文字列生成

#### Step 6: メンバー詳細でのアクティビティ表示

コンポーネントとして使われる場合を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | activity-feed.js | `ghost/admin/app/components/member/activity-feed.js` | コンポーネントロジック |
| 6-2 | activity-feed.hbs | `ghost/admin/app/components/member/activity-feed.hbs` | 5件表示、View all リンク |

**主要処理フロー（テンプレート）**:
- **9行目**: members-event-fetcher pageSize=5
- **61行目**: View all member activity リンク

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

```
MembersActivityRoute (ghost/admin/app/routes/members-activity.js)
    │
    └─ extends MembersManagementRoute
           └─ beforeModel() - canManageMembersチェック

MembersActivityController (ghost/admin/app/controllers/members-activity.js)
    │
    ├─ queryParams: [excludedEvents, member]
    │
    ├─ @use memberRecord = MemberFetcher(member)
    │
    ├─ hiddenEvents computed
    │      ├─ !member → EMAIL_EVENTS除外
    │      ├─ 常時 → aggregated_click_event除外
    │      └─ email無効 → EMAIL_EVENTS, NEWSLETTER_EVENTS除外
    │
    ├─ fullExcludedEvents
    │      └─ excludedEvents + hiddenEvents
    │
    └─ アクション
           ├─ changeExcludedEvents(newList)
           └─ changeMember(member)

members-activity.hbs (テンプレート)
    │
    ├─ MembersActivity::EventTypeFilter
    │      └─ onChange → changeExcludedEvents
    │
    ├─ MembersActivity::MemberFilter
    │      └─ onChange → changeMember
    │
    └─ members-event-fetcher
           │
           ├─ filter = members-event-filter
           │      └─ excludedEvents + member + post
           │
           ├─ loadEventsTask
           │      └─ ajax.request('/members/events')
           │
           └─ loadNextPage
                  └─ カーソルベースページネーション
```

### データフロー図

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

URLクエリパラメータ ───────▶ queryParams ─────────────────────▶ excludedEvents, member
(?excludedEvents=&member=)

member ───────────────────▶ MemberFetcher ───────────────────▶ memberRecord
                                 │
                                 └─ store.findRecord('member', id)

hiddenEvents ─────────────▶ fullExcludedEvents ──────────────▶ 完全な除外リスト
+ excludedEvents

fullExcludedEvents ───────▶ members-event-filter ────────────▶ フィルター文字列
+ member                         │
                                 └─ type:-[excludedEvents]+data.member_id:'{member}'

フィルター文字列 ─────────▶ members-event-fetcher ───────────▶ イベント配列
                                 │
                                 ├─ setup()
                                 │      └─ cursor = now
                                 │
                                 ├─ loadEventsTask()
                                 │      └─ GET /members/events
                                 │             filter: data.created_at:<'{cursor}'+{filter}
                                 │             limit: 50
                                 │
                                 └─ loadNextPage()
                                        └─ cursor = lastEvent.created_at
                                        └─ loadEventsTask()

イベント配列 ─────────────▶ parse-member-event ──────────────▶ 表示用イベント
                                 │
                                 └─ icon, action, info, object, url, route, description
                                 │
                                 ▼
                          MembersActivity::Table
                                 │
                                 └─ テーブル行表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| members-activity.js | `ghost/admin/app/routes/members-activity.js` | ルート | 権限チェック |
| members-activity.js | `ghost/admin/app/controllers/members-activity.js` | コントローラー | フィルター制御 |
| members-activity.hbs | `ghost/admin/app/templates/members-activity.hbs` | テンプレート | UIテンプレート |
| members-event-fetcher.js | `ghost/admin/app/helpers/members-event-fetcher.js` | ヘルパー | イベントフェッチ |
| members-event-filter.js | `ghost/admin/app/helpers/members-event-filter.js` | ヘルパー | フィルター生成 |
| parse-member-event.js | `ghost/admin/app/helpers/parse-member-event.js` | ヘルパー | イベント変換 |
| member-fetcher.js | `ghost/admin/app/helpers/member-fetcher.js` | ヘルパー | メンバー取得 |
| event-type-filter.hbs | `ghost/admin/app/components/members-activity/event-type-filter.hbs` | コンポーネント | イベントフィルター |
| member-filter.hbs | `ghost/admin/app/components/members-activity/member-filter.hbs` | コンポーネント | メンバーフィルター |
| table.hbs | `ghost/admin/app/components/members-activity/table.hbs` | コンポーネント | テーブル表示 |
| no-events.hbs | `ghost/admin/app/components/members-activity/no-events.hbs` | コンポーネント | 空状態 |
| activity-feed.js | `ghost/admin/app/components/member/activity-feed.js` | コンポーネント | メンバー詳細用 |
| activity-feed.hbs | `ghost/admin/app/components/member/activity-feed.hbs` | コンポーネント | メンバー詳細用UI |
