Skip to content

ユーザー個別配下の日報一覧に未チェックの一覧を追加した#8997

Merged
komagata merged 8 commits intomainfrom
feature/add-unchecked-reports-tab-for-mentor-only
Sep 3, 2025
Merged

ユーザー個別配下の日報一覧に未チェックの一覧を追加した#8997
komagata merged 8 commits intomainfrom
feature/add-unchecked-reports-tab-for-mentor-only

Conversation

@hirokiej
Copy link
Copy Markdown
Contributor

@hirokiej hirokiej commented Jul 30, 2025

Issue

概要

  • メンターでログイン時、メンターモードONでユーザーの日報ページに飛んだ際、日報一覧に「全て」と「未チェック」の一覧タブを追加しました。
  • ユーザーの日報タブの右上に「未チェックの日報数」を表示するバッヂも表示しました。
  • 日報の個別ページにそのユーザーの未チェックの日報数を表示したい #8702 で実装した、「◯◯さんの日報一覧へ」をクリックすると、未チェックの一覧タブに移動するようにしました。

変更確認方法

  1. feature/add-unchecked-reports-tab-for-mentor-onlyをローカルに取り込む
git fetch origin feature/add-unchecked-reports-tab-for-mentor-only
git checkout feature/add-unchecked-reports-tab-for-mentor-only
  1. foreman start -f Procfile.dev1でローカル環境を立ち上げる。
  2. ユーザー名komagata、passwordtesttestでログインをする。
  3. 左上のメンターモードがオンであることを確認する。
  4. hajimeの日報一覧に飛ぶ。
    i)タブの下に「全て」、「未チェック」のタブがあることを確認する。
    ii)「日報」の右上に「未チェックの日報数のバッヂ」があることを確認する。
  5. タブの表示・非表示の確認
    i)日報の隣のポートフォリオやコメントをクリックした際に「全て」、「未チェック」のタブが消えることを確認。
    ii)日報のタブに戻ってから、メンターモードをオフにし、「全て」、「未チェック」のタブが消えることを確認。
  6. メンターモードをオンに戻し、任意の日報に飛び、本文下の「hajimeさんの日報一覧へ」をクリックし、hajimeの「未チェックの日報一覧」に飛ぶことを確認する。

Screenshot

変更前

image

変更後

image

Summary by CodeRabbit

  • 新機能

    • ユーザー日報に「全て」「未チェック」タブを追加(管理者/メンター向け)
    • 日報タブに未チェック数のバッジを表示(管理者/メンターのみ)
    • メンター表示から未チェック日報一覧へ直接遷移するリンクを追加
    • 日本語ラベルを追加(全て/未チェック)
  • 変更

    • 未チェック日報の閲覧を管理者/メンターに限定(未許可は403)
    • 管理者/メンターのみタブナビゲーションを表示
    • ユーザー向け日報一覧の表示ロジックに「未チェック」切替を追加
  • リファクタ

    • 日報一覧のデータ取得ロジックを統一・簡素化し安定性を向上
    • 一部ページのメタ情報を削除(タイトル/説明の表示を廃止)

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 30, 2025

Walkthrough

APIとUsersのReportsコントローラにtarget=unchecked_reportsガードを追加し、未チェック日報はadmin_or_mentorのみ取得可能にした。フロントのReports.jsxはURL組み立てを一本化し、unchecked時にtargetパラメータを付与。ビューにタブナビと未チェックバッジを追加。

Changes

Cohort / File(s) Summary
Controllers: targetガードと分岐
app/controllers/api/reports_controller.rb, app/controllers/users/reports_controller.rb
API側で params[:target]=='unchecked_reports' の早期ガードを追加し、未承認なら403を返す。許可時は :checks を include し unchecked.not_wip スコープを適用。Users側は before_action で @target を設定し、@target に応じて取得ロジックを分岐(uncheckedは unchecked.not_wip を使用)。
Helper: バッジ追加
app/helpers/page_tabs/users_helper.rb
日報タブに管理者/メンターのみ表示する未チェック件数バッジ(Cache.user_unchecked_report_count(user))を追加。
Frontend: Reports取得の一本化
app/javascript/components/Reports.jsx
all prop と無駄な useEffect を削除。逐次的な URL ビルダーを導入し、unchecked 時に &target=unchecked_reports を追加。useSWR 呼び出しを単一化してデータ取得を簡素化。
Views: リンクとタブナビ
app/views/reports/_report_body.html.slim, app/views/users/reports/index.html.slim
mentor ブランチのユーザー日報リンクに target=unchecked_reports を付与。ユーザー日報一覧に「全て/未チェック」タブを追加し、選択に応じて Reports コンポーネントへ unchecked を渡す。
Locales: タブ文言
config/locales/ja.yml
ja.target.all_reports(全て)と ja.target.unchecked_reports(未チェック) を追加。

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Mentor as 管理者/メンター
  participant UI as Users Reports画面
  participant FE as Reports.jsx
  participant API as API::Reports#index
  participant DB as Report Scopes

  Mentor->>UI: /users/:id/reports?target=unchecked_reports を表示
  UI->>FE: unchecked=true を渡す
  FE->>API: GET /api/reports.json?user_id=:id&...&target=unchecked_reports
  API->>API: current_user.admin_or_mentor? を検証
  alt 権限あり
    API->>DB: apply unchecked.not_wip + filters (practice/user/company/limit)
    DB-->>API: records
    API-->>FE: 200 OK (JSON)
    FE-->>UI: 日報一覧表示
  else 権限なし
    API-->>FE: 403 Forbidden
    FE-->>UI: エラー表示
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • komagata

Poem

ぴょんと跳ねてタブが増えたよ、
未チェックは鍵を持つメンターへ、
バッジが光り道を示す、
URLはまっすぐ一本に、
ぴょん🐇春の報告祝い。

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 RuboCop (1.76.1)
app/controllers/api/reports_controller.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:162:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:82:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

app/controllers/users/reports_controller.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_loader.rb:58:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:162:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:82:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.79.2/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.79.2/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/add-unchecked-reports-tab-for-mentor-only

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch 3 times, most recently from 7f8f9f2 to f25d520 Compare August 4, 2025 17:29
@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch from f25d520 to f223c54 Compare August 16, 2025 05:32
@hirokiej hirokiej marked this pull request as ready for review August 16, 2025 17:07
@github-actions github-actions bot requested a review from komagata August 16, 2025 17:07
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
app/controllers/users/reports_controller.rb (1)

46-48: targetパラメータは許可リストでガードすることを提案

将来的なパラメータ乱用や想定外入力を避けるため、許可値のみを通す形にしておくと安全です。

-  def set_target
-    @target = params[:target] || 'all_reports'
-  end
+  def set_target
+    allowed = %w[all_reports unchecked_reports]
+    @target = allowed.include?(params[:target]) ? params[:target] : 'all_reports'
+  end
app/views/users/reports/index.html.slim (2)

8-8: mentor_login? ブロック内での class 条件は冗長です

この行は既に - if mentor_login? ブロック内のため、三項演算子は常に 'is-only-mentor' を返します。シンプルにできます。

-  nav.tab-nav(class="#{mentor_login? ? 'is-only-mentor' : ''}")
+  nav.tab-nav(class="is-only-mentor")

14-14: link_to の class 組み立てを簡潔にし、アクセシビリティ向上の aria-current を付与しましょう

配列を << で破壊的に結合する形は読み取りがやや難しいです。配列と compact を使うと簡潔で Rails 的です。あわせて、アクティブタブには aria-current="page" を付与すると支援技術での認識性が上がります。

-            = link_to t("target.#{target}"), user_reports_path(@user, target: target), class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link'
+            = link_to t("target.#{target}"),
+                      user_reports_path(@user, target: target),
+                      class: ['tab-nav__item-link', ('is-active' if @target == target)].compact,
+                      aria: { current: ('page' if @target == target) }
app/javascript/components/Reports.jsx (1)

28-34: practice_id が二重に付与され得るため一本化を推奨

practiceIduserPracticeId が同時に値を持つケースで practice_id が2回クエリに付与され、受け側の解釈次第では意図しない絞り込みになる恐れがあります。どちらか優先(通常はユーザー操作の userPracticeId を優先)にしてください。

-  if (practiceId) reportsUrl += `&practice_id=${practiceId}`
+  // userPracticeId(ユーザー操作)を優先し、なければ props の practiceId を使う
+  const selectedPracticeId = userPracticeId || practiceId
+  if (selectedPracticeId) reportsUrl += `&practice_id=${selectedPracticeId}`
   if (companyId) reportsUrl += `&company_id=${companyId}`
-  if (userPracticeId) reportsUrl += `&practice_id=${userPracticeId}`
   if (unchecked) reportsUrl += `&target=unchecked_reports`
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2b6bb5b and f223c54.

📒 Files selected for processing (7)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/helpers/page_tabs/users_helper.rb (2 hunks)
  • app/javascript/components/Reports.jsx (1 hunks)
  • app/views/reports/_report_body.html.slim (1 hunks)
  • app/views/users/reports/index.html.slim (1 hunks)
  • config/locales/ja.yml (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/api/reports_controller.rb
  • app/controllers/users/reports_controller.rb
  • app/helpers/page_tabs/users_helper.rb
🧬 Code Graph Analysis (3)
app/controllers/users/reports_controller.rb (2)
app/controllers/api/users_controller.rb (1)
  • before_action (3-87)
app/controllers/reports_controller.rb (1)
  • set_report (106-108)
app/helpers/page_tabs/users_helper.rb (2)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/models/cache.rb (1)
  • user_unchecked_report_count (15-19)
app/javascript/components/Reports.jsx (1)
app/javascript/user_mentor_memo.js (1)
  • userId (9-9)
🔇 Additional comments (7)
config/locales/ja.yml (1)

470-471: i18nキーの追加は妥当です

UIのタブ表示用に target.all_reports / target.unchecked_reports を用意した構成は既存の命名とも整合が取れており問題ありません。

app/helpers/page_tabs/users_helper.rb (2)

10-10: 日報タブへのバッジ追加は意図どおりでOK

badge: でメンター専用の未チェック件数を表示する意図は明確で、count: と役割が分かれているのも良いです。


38-42: キャッシュの無効化タイミングの確認をお願いします

Cache.user_unchecked_report_count(user) を利用しているため、チェック作成/削除時やWIP状態の変更時に該当ユーザーのキャッシュキー("#{user.id}-user_unchecked_report_count")が適切に削除・更新されることをご確認ください。未対応の場合、バッジが古い件数を表示する恐れがあります。必要であれば、Checkの作成/削除やReportのWIP変更時にキャッシュ削除するコールバックの追加をご提案できます。

app/views/reports/_report_body.html.slim (1)

17-17: メンター時の遷移先を未チェックタブに変更はOK

メンター導線として「未チェック」タブに誘導する変更はPRの目的に合致しています。target: 'unchecked_reports' の指定も適切です。

app/controllers/users/reports_controller.rb (1)

34-39: サーバーレンダリング側のフィルタは意図どおりでOK

@target == 'unchecked_reports' の場合のみ unchecked.not_wip を適用しており、WIPを除外した未チェック一覧になるため、要件に沿っています。API側の挙動も同様に揃えると一貫性が保てます(別コメント参照)。

app/views/users/reports/index.html.slim (1)

7-15: メンター限定タブの導入は整合性が取れていて良いです

  • target パラメータで「全て/未チェック」を切り替え、React 側に unchecked を渡す流れが明快です。
  • 既存のリンク(ユーザー日報一覧へ)とも動作が揃っており、UX 的にも自然です。
app/javascript/components/Reports.jsx (1)

28-35: URL 構築ロジックの一本化は読みやすくて良いです

分岐がなくなり、useSWR のキーも単純化されて保守性が上がっています。

li.tab-nav__item
= link_to t("target.#{target}"), user_reports_path(@user, target: target), class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link'

= react_component('Reports', userId: @user.id, practices: @current_user_practice.sorted_practices, unchecked: (@target == 'unchecked_reports'))
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

未チェック一覧のアクセス制御(メンター限定)を確認してください

URL 直叩きで一般ユーザーが target=unchecked_reports を指定した場合の挙動(無視 or 403 など)が API/コントローラ側で適切にガードされているか確認をお願いします。メンター UI は非表示でも、パラメータで閲覧できると情報露出になります。

以下スクリプトで関連箇所の有無とガード条件を確認できます(リポジトリルートで実行)。


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== unchecked_reports を扱うコントローラ周辺の定義確認 =='
rg -n -C5 -g 'app/controllers/**/*.rb' -P '(unchecked_reports|target\s*=>?\s*[\'"]unchecked_reports[\'"]|params\[:target\]\s*==\s*[\'"]unchecked_reports[\'"])'

echo
echo '== メンター/管理者ガードらしきメソッドの出現箇所確認 =='
rg -n -C3 -g 'app/controllers/**/*.rb' -P '(admin_or_mentor\?|mentor_login\?|mentor_mode)'

echo
echo '== users/reports_controller.rb と api/reports_controller.rb の set_target / フィルタ分岐の確認 =='
fd -t f reports_controller.rb app/controllers | xargs -I{} sh -c 'echo "--- {} ---"; sed -n "1,200p" {} | nl -ba | sed -n "1,200p" | rg -n -C3 -P "(before_action|set_target|unchecked|params\\[:target\\])" || true'

Length of output: 220


シェルスクリプト内の正規表現のクオート部分でパースエラーが発生しているようです。以下の修正版をリポジトリルートで再度実行し、結果をご共有ください。


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== unchecked_reports を扱うコントローラ周辺の定義確認 =='
rg -n -C5 -g 'app/controllers/**/*.rb' -P "unchecked_reports|target\\s*=>?\\s*['\"]unchecked_reports['\"]|params\\[:target\\]\\s*==\\s*['\"]unchecked_reports['\"]"

echo
echo '== メンター/管理者ガードらしきメソッドの出現箇所確認 =='
rg -n -C3 -g 'app/controllers/**/*.rb' -P "admin_or_mentor\\?|mentor_login\\?|mentor_mode"

echo
echo '== users/reports_controller.rb と api/reports_controller.rb の set_target / フィルタ分岐の確認 =='
fd -t f reports_controller.rb app/controllers | while read file; do
  echo "--- $file ---"
  rg -n -C3 -e "before_action" -e "set_target" -e "params\\[:target\\]" -e "unchecked" "$file" || true
done

Length of output: 14405


以下のスクリプトで API 側と UI 側それぞれのベースコントローラにメンター制御(before_action 等)が仕込まれているか確認してください。


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== app/controllers/api/base_controller.rb (1-200行) =='
sed -n '1,200p' app/controllers/api/base_controller.rb | nl -ba

echo
echo '== API::BaseController の before_action 一覧 =='
rg -n --color=never 'before_action' -g 'app/controllers/api/base_controller.rb' || echo 'no matches'

echo
echo '== app/controllers/application_controller.rb (1-200行) =='
sed -n '1,200p' app/controllers/application_controller.rb | nl -ba

echo
echo '== ApplicationController の before_action 一覧 =='
rg -n --color=never 'before_action' -g 'app/controllers/application_controller.rb' || echo 'no matches'

Length of output: 293


以下のスクリプトで API 側と UI 側それぞれのベースコントローラにメンター権限制御が設定されているか確認し、結果を共有してください。


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== API::BaseController (app/controllers/api/base_controller.rb lines 1-200) =='
if [[ -f app/controllers/api/base_controller.rb ]]; then
  sed -n '1,200p' app/controllers/api/base_controller.rb
  echo
  echo '== before_action in API::BaseController =='
  grep -n 'before_action' app/controllers/api/base_controller.rb || echo '== no before_action =='
  echo
  echo '== require_mentor_login_for_api in API::BaseController =='
  grep -n 'require_mentor_login_for_api' app/controllers/api/base_controller.rb || echo '== no require_mentor_login_for_api =='
  echo
  echo '== require_admin_or_mentor_login in API::BaseController =='
  grep -n 'require_admin_or_mentor_login' app/controllers/api/base_controller.rb || echo '== no require_admin_or_mentor_login =='
else
  echo 'app/controllers/api/base_controller.rb not found'
fi

echo
echo '== ApplicationController (app/controllers/application_controller.rb lines 1-200) =='
if [[ -f app/controllers/application_controller.rb ]]; then
  sed -n '1,200p' app/controllers/application_controller.rb
  echo
  echo '== before_action in ApplicationController =='
  grep -n 'before_action' app/controllers/application_controller.rb || echo '== no before_action =='
  echo
  echo '== require_mentor_login in ApplicationController =='
  grep -n 'require_mentor_login' app/controllers/application_controller.rb || echo '== no require_mentor_login =='
  echo
  echo '== require_admin_or_mentor_login in ApplicationController =='
  grep -n 'require_admin_or_mentor_login' app/controllers/application_controller.rb || echo '== no require_admin_or_mentor_login =='
else
  echo 'app/controllers/application_controller.rb not found'
fi

Length of output: 4874


未チェック一覧へのアクセス制御を強化してください
現状、Users::ReportsController(UI側)と API::ReportsController(API側)いずれも target=unchecked_reports を指定すると誰でも未チェック一覧を取得できてしまいます。メンター以外のアクセスを禁止するため、以下の修正をお願いします。

  • app/controllers/users/reports_controller.rb

    • クラス定義直後に
      before_action :require_admin_or_mentor_login
      を追加し、Web UI 側の /users/:user_id/reports アクションをメンター限定にする
  • app/controllers/api/reports_controller.rb

    • コントローラ先頭に
      before_action :require_mentor_login_for_api, only: :index
      を追加し、API 経由で target=unchecked_reports を扱う際には JWT 認証+メンター権限を必須化する
    • もしくは index 内の
      if params[:target] == 'unchecked_reports'
      の前後で head :forbidden unless current_user.admin_or_mentor? のような明示的ガードを入れる

これにより、パラメータ直接指定による情報漏洩を防止できます。

🤖 Prompt for AI Agents
In app/views/users/reports/index.html.slim around line 16: the view passes
target=unchecked_reports which currently allows anyone to fetch unchecked
reports; to enforce mentor-only access add controller guards: in
app/controllers/users/reports_controller.rb (class start) add before_action
:require_admin_or_mentor_login to restrict the web UI /users/:user_id/reports to
mentors/admins; in app/controllers/api/reports_controller.rb (top of controller)
add before_action :require_mentor_login_for_api, only: :index to require
JWT+mentor for API index, or alternatively inside index add an explicit guard
(e.g. head :forbidden unless current_user&.admin_or_mentor?) immediately before
handling if params[:target] == 'unchecked_reports' so parameter-based access to
unchecked reports is forbidden for non-mentors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

それぞれ、head :forbiddenで対応。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej head :forbidden での対応で問題ありません。これにより一般ユーザーが target=unchecked_reports パラメータを直接指定してもアクセスが拒否され、メンター限定機能として適切に保護されます。

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch from f223c54 to 0d76628 Compare August 17, 2025 08:18
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/controllers/api/reports_controller.rb (1)

11-16: API 側の未チェック絞り込みはスコープに寄せ、includes(:checks) は不要

過去コメントのとおり(開発者も同意済み)、モデルスコープで統一すると読みやすく、WIP 除外も明示できます。includes(:checks) はこの用途では不要です。

-    if params[:target] == 'unchecked_reports'
-      unless current_user.admin_or_mentor?
-        return head :forbidden
-      end
-      @reports = @reports.includes(:checks).unchecked.not_wip
-    end
+    if params[:target] == 'unchecked_reports'
+      return head :forbidden unless current_user.admin_or_mentor?
+      @reports = @reports.unchecked.not_wip
+    end
🧹 Nitpick comments (4)
app/helpers/page_tabs/users_helper.rb (2)

10-10: count に length を使うと関連を全件ロードしやすい—sizecount を推奨

関連が未ロード時は sizeCOUNT(*) に、ロード済みならメモリ上の長さを返します。length は常にロードを誘発するため非効率です。

-      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.length, badge: unchecked_report_badge(current_user:, user:) }
+      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.size, badge: unchecked_report_badge(current_user:, user:) }

38-42: バッジは0件時に非表示にするか要件確認。ついでに nil セーフに

現仕様だとメンター/管理者には 0 件でもバッジが出る可能性があります。UX的に 0 は非表示にするなら以下のようにします。current_user が nil のケースにも安全です。

-    def unchecked_report_badge(current_user:, user:)
-      return nil unless current_user.admin_or_mentor?
-
-      Cache.user_unchecked_report_count(user)
-    end
+    def unchecked_report_badge(current_user:, user:)
+      return nil unless current_user&.admin_or_mentor?
+      count = Cache.user_unchecked_report_count(user)
+      return nil if count.zero?
+      count
+    end

補足: 「0 件時も表示する」仕様であればこの変更は不要です。どちらが正か教えてください。

app/javascript/components/Reports.jsx (2)

35-35: SWR のキーは OK。クエリ構築は URLSearchParams 化で可読性・安全性向上(任意)

文字列連結でも動きますが、URLSearchParams を使うとエンコード漏れや二重付与の防止がしやすくなります。

例:

const params = new URLSearchParams({ page: String(page) })
if (userId) params.set('user_id', userId)
if (companyId) params.set('company_id', companyId)
const pid = userPracticeId || practiceId
if (pid) params.set('practice_id', pid)
if (unchecked) params.set('target', 'unchecked_reports')

const reportsUrl = `/api/reports.json?${params.toString()}`
const { data, error } = useSWR(reportsUrl, fetcher)

28-35: おまけ: 自己代入の useEffect は削除可能

useEffect(() => { setUserPracticeId(userPracticeId) }, [userPracticeId]) は実質ノーオペです。ノイズ削減のため削除を推奨します。

以下は選択行範囲外のため参考コードです(実装は該当行を削除してください):

// 削除して問題ありません
// useEffect(() => {
//   setUserPracticeId(userPracticeId)
// }, [userPracticeId])
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f223c54 and 0d76628.

📒 Files selected for processing (7)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/helpers/page_tabs/users_helper.rb (2 hunks)
  • app/javascript/components/Reports.jsx (1 hunks)
  • app/views/reports/_report_body.html.slim (1 hunks)
  • app/views/users/reports/index.html.slim (1 hunks)
  • config/locales/ja.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • config/locales/ja.yml
  • app/views/reports/_report_body.html.slim
  • app/controllers/users/reports_controller.rb
  • app/views/users/reports/index.html.slim
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/api/reports_controller.rb
  • app/helpers/page_tabs/users_helper.rb
🧠 Learnings (1)
📚 Learning: 2025-07-23T20:31:13.856Z
Learnt from: jun-kondo
PR: fjordllc/bootcamp#8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Applied to files:

  • app/controllers/api/reports_controller.rb
🧬 Code Graph Analysis (3)
app/controllers/api/reports_controller.rb (1)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/helpers/page_tabs/users_helper.rb (2)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/models/cache.rb (1)
  • user_unchecked_report_count (15-19)
app/javascript/components/Reports.jsx (1)
app/javascript/user_mentor_memo.js (1)
  • userId (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (2)
app/helpers/page_tabs/users_helper.rb (1)

10-10: PageTabsComponent は badge に対応済みです

PageTabComponent の初期化子で badge: を受け取り、badge&.positive? の判定でバッジを描画する実装が既に存在するため、該当のヘルパーから渡した badge: は正しく表示されます。この指摘は無視して問題ありません。

app/controllers/api/reports_controller.rb (1)

11-16: unchecked_reports はユーザー個別用途想定。user_id 必須にするか要件確認

現状、メンター/管理者であれば user_id なしでも全体の未チェック一覧を取得可能です。仕様上「ユーザー個別配下の日報一覧」に限定するなら params[:user_id] の存在を必須にしておくと意図しない使われ方を防げます(UI は個別ユーザーでしか使用しない前提でも、API は汎用なので注意)。

例(必要なら):

if params[:target] == 'unchecked_reports'
  return head :forbidden unless current_user.admin_or_mentor?
  return head :bad_request if params[:user_id].blank? # 個別ページ限定にする場合
  @reports = @reports.unchecked.not_wip
end

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch 3 times, most recently from bda6473 to 10380eb Compare August 17, 2025 15:29
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/javascript/components/Reports.jsx (1)

27-28: practice_id の二重付与問題の解消、GJです

const pid = userPracticeId || practiceId によってフィルタ優先度が明確になり、重複付与が解消されています。

🧹 Nitpick comments (2)
app/javascript/components/Reports.jsx (2)

24-29: クエリ文字列の構築は URLSearchParams で堅牢化を

現在の文字列連結でも動作しますが、将来的なパラメータ追加や値のエンコードを考慮すると URLSearchParams を使うと安全・可読です(SWR のキー安定性も向上)。
以下の置き換えを提案します。

-  let reportsUrl = `/api/reports.json?page=${page}`
-  if (userId) reportsUrl += `&user_id=${userId}`
-  if (companyId) reportsUrl += `&company_id=${companyId}`
-  const pid = userPracticeId || practiceId
-  if (pid) reportsUrl += `&practice_id=${pid}`
-  if (unchecked) reportsUrl += `&target=unchecked_reports`
+  const params = new URLSearchParams({ page: String(page) })
+  if (userId) params.set('user_id', String(userId))
+  if (companyId) params.set('company_id', String(companyId))
+  const pid = userPracticeId || practiceId
+  if (pid) params.set('practice_id', String(pid))
+  if (unchecked) params.set('target', 'unchecked_reports')
+  const reportsUrl = `/api/reports.json?${params.toString()}`

24-29: フィルタ変更時にページ番号を 1 にリセットすると UX が向上します

practice や unchecked の切り替え時に前の page を引きずると「空ページ」に飛ぶ可能性があります。userPracticeId/userId/companyId/unchecked が変わったら setPage(1) するのが無難です(optional)。
実装例(別箇所に追加。useEffect の import も併せて復帰が必要):

// 例: setUserPracticeId の定義付近に
useEffect(() => {
  setPage(1)
}, [userPracticeId, userId, companyId, unchecked])
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bda6473 and 10380eb.

📒 Files selected for processing (1)
  • app/javascript/components/Reports.jsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (3)
app/javascript/components/Reports.jsx (3)

1-1: 不要な useEffect を削除したのは妥当です

副作用がなくなったため import の整理は正しいです。


31-31: SWR を単一キーに統一したのはシンプルで良いです

URL 生成の一本化により分岐が減り、読みやすさ/保守性が向上しています。


29-29: 権限制御の確認完了
フロントから target=unchecked_reports を付与した場合でも、以下のコントローラで current_user.admin_or_mentor? による認可チェックが行われていることを確認しました。

  • app/controllers/users/reports_controller.rb(set_reports 内):36 行目
  • app/controllers/api/reports_controller.rb:12 行目

これにより、メンター/管理者以外はアクセスできない仕様になっています。問題ありません。

@hirokiej hirokiej requested a review from tyrrell-IH August 17, 2025 15:53
@hirokiej
Copy link
Copy Markdown
Contributor Author

@tyrrell-IH さん
お疲れ様です。
レビュー依頼です!
こちらお願いしても良いでしょうか🙇?

@tyrrell-IH
Copy link
Copy Markdown
Contributor

tyrrell-IH commented Aug 18, 2025

@hirokiej

すいません、8/20〜8/26で帰省する予定でその間あまり稼働できません。
また、子供の夏休み終わり・夏休み明けはバタバタして、そちらもあまり稼働できない可能性があります。。。

自身のPRの方も進めたいので、9/8(月)期限とかでよろしければレビューできますがいかがでしょうか?

もし難しければ、他の方にレビューをご依頼いただければと思います🙏

@hirokiej
Copy link
Copy Markdown
Contributor Author

@tyrrell-IH さん
ご返信ありがとうございます!
承知しました💡
次の機会があればぜひお願いいたします🙇

@sjabcdefin さん
お疲れ様です。
レビュー依頼です!
こちらお願いしても良いでしょうか🙇?

@hirokiej hirokiej requested review from sjabcdefin and removed request for tyrrell-IH August 18, 2025 07:10
@sjabcdefin
Copy link
Copy Markdown
Contributor

@hirokiej
お疲れさまです。😌
レビューの件、かしこまりました!

@sjabcdefin
Copy link
Copy Markdown
Contributor

@hirokiej
お疲れさまです。😌
本日のリリース作業により、mainとの差分が大量に出るようになってしまっているようです。😮😵
お手数ですが、今回の修正分だけの差分となるように対応していただけますでしょうか?
よろしくお願いいたします。😌🙏

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch from 10380eb to e2e1327 Compare August 19, 2025 16:02
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
app/helpers/page_tabs/users_helper.rb (2)

10-10: count は length ではなく size を推奨(無駄なレコードロードを回避)

length は関連をロード済みの場合に全件をメモリに展開する可能性がありコストが高いです。size なら未ロード時は COUNT(*) にフォールバックします。

-      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.length, badge: unchecked_report_badge(current_user:, user:) }
+      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.size, badge: unchecked_report_badge(current_user:, user:) }

38-42: current_user が nil の場合に NoMethodError の可能性(nil セーフに)

未ログイン状態でこのヘルパが呼ばれるコンテキストがある場合、current_user.admin_or_mentor? が例外になります。nil セーフにしておくと安全です。

-    def unchecked_report_badge(current_user:, user:)
-      return nil unless current_user.admin_or_mentor?
-
-      Cache.user_unchecked_report_count(user)
-    end
+    def unchecked_report_badge(current_user:, user:)
+      return nil unless current_user&.admin_or_mentor?
+
+      Cache.user_unchecked_report_count(user)
+    end

補足: PageTabsComponent 側で badge オプションの描画が実装済みであることも合わせてご確認ください(未実装でも本変更は無害ですが、表示されない可能性があります)。

app/views/users/reports/index.html.slim (1)

7-16: クラス指定の簡素化と可読性向上(配列操作の << は避けたい)

  • if mentor_login? ブロック内でさらに class="#{mentor_login? ? ...}" を評価しているのは二重判定です。ここは静的に .is-only-mentor を付けてよいです。
  • class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link' は配列へ副作用的に << しており読み取りにくいです。['tab-nav__item-link', ('is-active' if 条件)] などの形がシンプルです。
  • 可能なら aria-current を付けてアクセシビリティを向上させると尚良いです。
-- if mentor_login?
-  nav.tab-nav(class="#{mentor_login? ? 'is-only-mentor' : ''}")
+- if mentor_login?
+  nav.tab-nav.is-only-mentor
     .container
       ul.tab-nav__items
         - targets = %w[all_reports unchecked_reports]
         - targets.each do |target|
           li.tab-nav__item
-            = link_to t("target.#{target}"), user_reports_path(@user, target: target), class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link'
+            = link_to t("target.#{target}"),
+              user_reports_path(@user, target: target),
+              class: ['tab-nav__item-link', ('is-active' if @target == target)],
+              aria: { current: (@target == target ? 'page' : nil) }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 10380eb and e2e1327.

📒 Files selected for processing (6)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/helpers/page_tabs/users_helper.rb (2 hunks)
  • app/javascript/components/Reports.jsx (2 hunks)
  • app/views/reports/_report_body.html.slim (1 hunks)
  • app/views/users/reports/index.html.slim (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/controllers/api/reports_controller.rb
  • app/javascript/components/Reports.jsx
  • app/controllers/users/reports_controller.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit Configuration File

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/helpers/page_tabs/users_helper.rb
🧬 Code Graph Analysis (1)
app/helpers/page_tabs/users_helper.rb (2)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/models/cache.rb (1)
  • user_unchecked_report_count (15-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (1)
app/views/reports/_report_body.html.slim (1)

17-17: メンター向け導線として適切です(LGTM)

target: 'unchecked_reports' を付与して未チェック一覧へ遷移させる変更は意図通りに見えます。権限制御はコントローラ側で head :forbidden 等を入れている前提で問題ありません。

@hirokiej
Copy link
Copy Markdown
Contributor Author

@sjabcdefin さん
お疲れ様です!
とんでもないことになっていて驚きました💦笑
修正いたしましたので、再度ご確認よろしくお願いいたします🙇

Copy link
Copy Markdown
Contributor

@sjabcdefin sjabcdefin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

お疲れさまです。😌
コメント残させていただきました!
ご確認お願いいたします。😌🙏

render PageTabsComponent.new(tabs:, active_tab:)
end

def unchecked_report_badge(current_user:, user:)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unchecked_report_badgeメソッドについて、以下をuser_page_tabsメソッド内にまとめても良さそうに思いました!

unchecked_report_badge = current_user.admin_or_mentor? ? Cache.user_unchecked_report_count(user) : nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに、その方がわかりやすくて良いと思いました。
修正します!

= user_page_tabs(@user, active_tab: '日報')

= react_component('Reports', userId: @user.id, practices: @current_user_practice.sorted_practices)
- if mentor_login?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admin ロールのみを持つユーザー(adminonly)でログインした際に、[全て] [未チェック]タブが表示されませんでした。
admin ユーザーでも [全て] [未チェック] タブは表示される仕様だと認識しています。
この部分が該当箇所かと思いますので、ご確認いただけると幸いです。😌

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

勝手な思い込みでadminroleとしてメンターも含まれていると勘違いしておりました。
ご確認ありがとうございます🙇
修正します!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

管理者ユーザ(adminonly)でログインした際に、[全て/未チェック]タブが表示されるようになりました!
ご対応ありがとうございます。😌🙏

すみません。もう1点ご確認いただけますでしょうか。 管理者ユーザ(adminonly)でメンターモードOFFのときに、[全て/未チェック]タブが表示されるままのようです。 メンターモードオフのときは、[全て/未チェック]タブが表示されない仕様だと認識しています。
ご確認いただけると幸いです。😌

- targets = %w[all_reports unchecked_reports]
- targets.each do |target|
li.tab-nav__item
= link_to t("target.#{target}"), user_reports_path(@user, target: target), class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user_reports_path(@user, target: target)について、user_reports_path(@user, target:)で問題ないと思います!!(target省略)


def set_reports
@reports = user.reports.list.page(params[:page])
@reports = case @target
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

分岐が2つしかないので、case ではなく if/else で十分ではないかと思いました。🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

caseからifを足したときにどうにかできないかな?と思ったのですが、==をを使えば、if/elseでいけましたね…!
修正します!

@reports = user.reports.list.page(params[:page])
@reports = case @target
when 'unchecked_reports'
head :forbidden unless current_user.admin_or_mentor?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebUIからの操作だと、head :forbidden ではなく、「管理者・メンターとしてログインしてください」というメッセージを表示し、元のページにリダイレクトしたほうがユーザーフレンドリーではないかと思いました。🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別のページではどうなっているかを怠っていました💦

ユーザーフレンドリーではないかと思いました。🤔

それとは別でユーザーフレンドリーという部分の考えもなかったので勉強になりました🙇
修正します!

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch from e2e1327 to a476bef Compare August 24, 2025 09:40
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
app/helpers/page_tabs/users_helper.rb (1)

5-12: 過去指摘の「バッジ計算の集約」対応はOKです

user_page_tabs 内でバッジ値を完結させた実装により、責務が明確になりました。

app/views/users/reports/index.html.slim (1)

14-14: Shorthand引数(target:)の利用は問題なし

user_reports_path(@user, target:) の省略記法はRuby 3系で妥当です。可読性も良いです。

🧹 Nitpick comments (7)
app/controllers/users_controller.rb (1)

68-68: トークン生成はモデル側へ委譲(has_secure_token)し、Fat Controller を回避しましょう

コントローラでトークンを生成すると責務が散らばります。User モデルに has_secure_token :unsubscribe_email_token を定義すれば、作成時に自動生成+再生成 API(regenerate_...)が使え、重複にも強くなります。ガイドライン的にも Controller を痩せさせるのが吉です。

この差分のみの修正案(本ファイル内):

-    @user.unsubscribe_email_token = SecureRandom.urlsafe_base64
+    # User.has_secure_token :unsubscribe_email_token により自動生成されるため不要

サポートとなる変更(他ファイル例):

# app/models/user.rb
class User < ApplicationRecord
  has_secure_token :unsubscribe_email_token
end

任意の堅牢化(既存レコード対策・一意制約):

  • 既存ユーザーの nil 埋めを行う rake/migration(nil のみに token を付与)
  • DB にユニークインデックスを付与(存在しない場合)
config/initializers/active_support_notifications.rb (1)

28-29: 購読登録を一度きりにするヘルパー化を検討してください

説明
Rails.application.reloader.to_prepare 内で ActiveSupport::Notifications.subscribe を呼び出すと、開発環境のコードリロード時に購読が重複登録され続けるリスクがあります。将来的なデバッグ容易性やメモリ効率を考慮し、subscribe_once のような「一度だけ登録」するラッパーヘルパーの導入をおすすめします。

対象箇所

  • config/initializers/active_support_notifications.rb の 26〜29行

差分例

-  ActiveSupport::Notifications.subscribe('regular_event.update', RegularEventUpdateNotifier.new)
-  ActiveSupport::Notifications.subscribe('student_or_trainee.create', TimesChannelCreator.new)
+  Notifications.subscribe_once('regular_event.update', RegularEventUpdateNotifier)
+  Notifications.subscribe_once('student_or_trainee.create', TimesChannelCreator)

ヘルパー実装例

# config/initializers/notifications_helper.rb
module Notifications
  mattr_accessor :handles, default: {}

  def self.subscribe_once(event, klass)
    # すでに購読ハンドルがあれば解除
    ActiveSupport::Notifications.unsubscribe(handles[event]) if handles[event]
    # 新たに購読してハンドルを保持
    handles[event] = ActiveSupport::Notifications.subscribe(event, klass.new)
  end
end

その他の subscribe 呼び出しも同様に見直し、一度きりの登録に置き換えることで、多重購読による意図しない副作用を防止してください。

app/helpers/page_tabs/users_helper.rb (2)

7-7: nilセーフティと0件時の非表示(任意)

未ログイン等でcurrent_userがnilの可能性を排除できない場合に備え、安全側に倒すのが無難です。ついでに0件時はバッジを非表示にする(nilにする)とUI的に自然です。

-      unchecked_report_badge = current_user.admin_or_mentor? ? Cache.user_unchecked_report_count(user) : nil
+      count = current_user&.admin_or_mentor? ? Cache.user_unchecked_report_count(user) : nil
+      unchecked_report_badge = count.to_i.positive? ? count : nil

この画面は常にログイン前提でしょうか?未ログインアクセスがあり得るなら上記変更をご検討ください。ゼロ件表示の是非も仕様確認をお願いします。


11-11: lengthsizeで不要なレコードロードを回避

user.reports.lengthは未ロード時に全件ロードします。sizeはキャッシュ済なら配列長、未ロードなら COUNT(*) になり効率的です。

-      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.length, badge: unchecked_report_badge }
+      tabs << { name: '日報', link: user_reports_path(user), count: user.reports.size, badge: unchecked_report_badge }
app/views/users/reports/index.html.slim (1)

14-14: クラス属性の生成をclass_namesで簡潔に(任意)

配列へ<<で破壊的追加するより、Railsのclass_namesを使うと読みやすくなります(Rails 7+)。

-            = link_to t("target.#{target}"), user_reports_path(@user, target:), class: (@target == target ? ['is-active'] : []) << 'tab-nav__item-link'
+            = link_to t("target.#{target}"),
+                      user_reports_path(@user, target:),
+                      class: class_names('tab-nav__item-link', 'is-active': @target == target)
app/models/user_course_practice.rb (1)

96-98: Query Objectへの委譲はGood

複雑なクエリの切り出しでモデルの責務が軽くなりました。キャッシュ(メモ化)も維持されており問題ありません。

sorted_practicesの並び(courses→categories)とQuery側の並び(categories→courses)が逆順です。UI要件に合わせて揃える必要があれば、Query側のorderを調整してください。

test/queries/user_unstarted_practices_query_test.rb (1)

55-57: 重複排除と並び順のテスト追加を提案(任意)

distinct追加の回帰を防ぐため、重複なし・期待順序の検証を1ケースずつ追加するのが安全です。

@@
   test 'should return different results for different users' do
     user1 = users(:komagata)
     user2 = users(:machida)
 
     result1 = UserUnstartedPracticesQuery.new(user: user1).call
     result2 = UserUnstartedPracticesQuery.new(user: user2).call
 
     assert_not_equal result1.to_a.sort, result2.to_a.sort
   end
+
+  test 'should not include duplicate practices' do
+    user = users(:komagata)
+    result = UserUnstartedPracticesQuery.new(user:).call
+    assert_equal result.map(&:id).uniq, result.map(&:id)
+  end
+
+  # 並び順が仕様に含まれる場合のみ有効化
+  # test 'should be ordered by categories_practices and courses_categories position' do
+  #   user = users(:komagata)
+  #   result = UserUnstartedPracticesQuery.new(user:).call
+  #   sorted_ids = result.sort_by { |p|
+  #     [
+  #       p.categories_practices.first&.position || 0,
+  #       p.categories.first&.courses_categories.first&.position || 0
+  #     ]
+  #   }.map(&:id)
+  #   assert_equal sorted_ids, result.map(&:id)
+  # end
 end

並び順の仕様がUI依存であればコメントアウトのままでもOKです。仕様が固まっている場合は有効化をご検討ください。

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e2e1327 and a476bef.

📒 Files selected for processing (13)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/controllers/users_controller.rb (1 hunks)
  • app/helpers/page_tabs/users_helper.rb (1 hunks)
  • app/javascript/components/Reports.jsx (2 hunks)
  • app/models/sign_up_notifier.rb (0 hunks)
  • app/models/user_course_practice.rb (1 hunks)
  • app/queries/user_unstarted_practices_query.rb (1 hunks)
  • app/views/reports/_report_body.html.slim (1 hunks)
  • app/views/users/reports/index.html.slim (1 hunks)
  • config/initializers/active_support_notifications.rb (1 hunks)
  • config/locales/ja.yml (1 hunks)
  • test/queries/user_unstarted_practices_query_test.rb (1 hunks)
💤 Files with no reviewable changes (1)
  • app/models/sign_up_notifier.rb
🚧 Files skipped from review as they are similar to previous changes (5)
  • config/locales/ja.yml
  • app/controllers/api/reports_controller.rb
  • app/javascript/components/Reports.jsx
  • app/views/reports/_report_body.html.slim
  • app/controllers/users/reports_controller.rb
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/models/user_course_practice.rb
  • test/queries/user_unstarted_practices_query_test.rb
  • app/controllers/users_controller.rb
  • config/initializers/active_support_notifications.rb
  • app/queries/user_unstarted_practices_query.rb
  • app/helpers/page_tabs/users_helper.rb
test/**/*

⚙️ CodeRabbit configuration file

test/**/*: # Test

  • TestCase名は英語で書く。
  • どうしても避けられない時以外にsystem testでsleepは使わない。

Unit Test

model, helper, decorator, view_componentについてはメソッドを追加した場合は必ず対応したUnit TestのTestCaseを1つは書く。

Files:

  • test/queries/user_unstarted_practices_query_test.rb
🧠 Learnings (1)
📚 Learning: 2025-07-23T20:31:13.856Z
Learnt from: jun-kondo
PR: fjordllc/bootcamp#8977
File: app/controllers/reports_controller.rb:63-63
Timestamp: 2025-07-23T20:31:13.856Z
Learning: fjordllc/bootcampプロジェクトの`app/controllers/reports_controller.rb`において、`create`と`update`アクションは両方とも`report.save_uniquely`を使用し、同じ`:report_save`イベントと`'report.save'`イベントを発行する。これは両方とも本質的に「レポートの保存」操作であり、作成と更新を区別する必要がないためである。

Applied to files:

  • app/views/users/reports/index.html.slim
🧬 Code graph analysis (2)
config/initializers/active_support_notifications.rb (2)
app/controllers/users_controller.rb (1)
  • new (52-57)
app/controllers/regular_events_controller.rb (1)
  • new (17-24)
app/helpers/page_tabs/users_helper.rb (2)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/models/cache.rb (1)
  • user_unchecked_report_count (15-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (5)
app/controllers/users_controller.rb (1)

60-75: 歓迎: 学生/研修生イベントへの一本化に沿った変更

create フローの前段でトークンを付与し、その後の分岐(create_free_user! / create_user!)に影響しない位置に置かれている点は妥当です。Strong Parameters に unsubscribe_email_token を含めていないのも安全です。

app/views/users/reports/index.html.slim (2)

7-10: 提案された条件式の修正は不要です

admin_or_mentor_login? は管理者ユーザーに対しても true を返すよう、以下のように既に実装されています。

  • app/controllers/concerns/authentication/login_helpers.rb(27行目)
    def admin_or_mentor_login?
      logged_in? && current_user.admin_or_mentor?
    end
  • app/models/user.rb(662–664行目)
    def admin_or_mentor?
      admin? || mentor?
    end

したがって、

- if admin_or_mentor_login?
+ if current_user&.admin? || mentor_login?

への書き換えは既存ロジックと同等であり、根本的な解決にはなりません。

Likely an incorrect or invalid review comment.


16-16: Reportsコンポーネント呼び出しの確認結果
以下5箇所でreact_component('Reports', …)が使われていることをスクリプトで確認しました。

  • app/views/practices/reports/index.html.slim:14
  • app/views/users/reports/index.html.slim:16
  • app/views/talks/show.html.slim:105
  • app/views/current_user/reports/index.html.slim:23
  • app/views/companies/reports/index.html.slim:21

また、all:プロップの残存もスクリプトで検索しましたがヒットしませんでした。

結論
新設のuncheckedはデフォルトでfalse(従来の全件表示相当)となっており、既存の呼び出しに手を加える必要はありません。

app/queries/user_unstarted_practices_query.rb (1)

23-26: サブクエリの形は妥当(補足)

where.not(id: subquery)SELECT practice_id FROM learnings...をそのまま渡す形はDB最適化されやすく、良い選択です。

test/queries/user_unstarted_practices_query_test.rb (1)

5-57: 基本ケースの網羅は十分です

未着手のみ抽出、status別の除外、ユーザー差分の検証が揃っていて回帰に強いです。

Comment on lines +14 to +21
def query
relation
.joins(categories_practices: { category: :courses_categories })
.where(courses_categories: { course_id: @course_id })
.where.not(id: learned_practice_ids)
.select('practices.*')
.order('categories_practices.position ASC, courses_categories.position ASC')
end
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

重複行の混入防止のためdistinctを付与してください

categories_practices/courses_categoriesへのJOINで同一Practiceが複数カテゴリに属するケースだと重複が返ります。UI/集計に影響が出るためDISTINCTを推奨します。

   def query
     relation
       .joins(categories_practices: { category: :courses_categories })
       .where(courses_categories: { course_id: @course_id })
       .where.not(id: learned_practice_ids)
-      .select('practices.*')
+      .select('practices.*')
+      .distinct
       .order('categories_practices.position ASC, courses_categories.position ASC')
   end

本件対応後、結果件数/並び順が期待どおりか(重複が消え、順序は維持されるか)を実データ/テストでご確認ください。


🏁 Script executed:

#!/bin/bash
set -e

# Locate the query file
query_file=$(fd -t f 'user_unstarted_practices_query.rb')
echo "Found file: $query_file"

# Show the method implementation
echo "----- user_unstarted_practices_query.rb (first 200 lines) -----"
sed -n '1,200p' "$query_file"

# Search for specs referencing this query
echo "----- Specs referencing UserUnstartedPracticesQuery -----"
rg -n 'UserUnstartedPracticesQuery' -C3 --glob '*_spec.rb' || echo "No specs found for UserUnstartedPracticesQuery"

Length of output: 1618


user_unstarted_practices_query.rb.distinct を追加して重複行を排除

カテゴリ結合(joins(categories_practices: { category: :courses_categories }))を行うと、同一の Practice が複数のカテゴリに属するケースで重複レコードが返される恐れがあります。UI や集計への影響を避けるため、SQL レベルで DISTINCT を付与してください。

対象箇所:

  • app/queries/user_unstarted_practices_query.rbdef query メソッド内

修正例:

   def query
     relation
       .joins(categories_practices: { category: :courses_categories })
       .where(courses_categories: { course_id: @course_id })
       .where.not(id: learned_practice_ids)
-      .select('practices.*')
+      .select('practices.*')
+      .distinct
       .order('categories_practices.position ASC, courses_categories.position ASC')
   end

追加で必要な対応:

  • .distinct を追加後、重複が排除されつつソート順が維持されるか、実データやテストで必ず検証してください
  • 現状、このクエリを直接参照するRSpec 等のテストが存在しないようです。重複チェックを含むテストケース(たとえば、同一 Practice が複数カテゴリに属した場合に 1 件しか返らないことを確認するテスト)を新規に実装してください

上記対応により、クエリ結果の正確性と並び順の維持が担保されます。

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def query
relation
.joins(categories_practices: { category: :courses_categories })
.where(courses_categories: { course_id: @course_id })
.where.not(id: learned_practice_ids)
.select('practices.*')
.order('categories_practices.position ASC, courses_categories.position ASC')
end
def query
relation
.joins(categories_practices: { category: :courses_categories })
.where(courses_categories: { course_id: @course_id })
.where.not(id: learned_practice_ids)
.select('practices.*')
.distinct
.order('categories_practices.position ASC, courses_categories.position ASC')
end
🤖 Prompt for AI Agents
In app/queries/user_unstarted_practices_query.rb around lines 14-21, the query
can return duplicate Practice rows when a practice belongs to multiple
categories; add .distinct to the ActiveRecord chain (keeping the existing
select/order) so duplicates are removed at SQL level, then run/adjust tests and
add a new RSpec that creates a Practice associated with multiple categories to
assert the query returns a single record and that ordering is preserved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mainとの差分が混ざってしまっていたので、上記は本PRとは関係ないと思います

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej その通りです。app/queries/user_unstarted_practices_query.rb は今回のPR(未チェック日報一覧の追加)とは全く関係のないファイルですね。mainブランチとの差分が混ざってしまったことで、本来のPR変更分ではないファイルにコメントしてしまいました。

私の前回のコメントは無視していただいて構いません。本PRの実際の変更内容(日報一覧のタブ追加、API修正、フロントエンド更新など)に焦点を当てたレビューを行います。

ご指摘いただき、ありがとうございました。

  (\   /)
 ( ._. )
o_(")(")  *confused but learning*

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch 2 times, most recently from 8c8a51b to 525bf87 Compare August 24, 2025 11:56
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/controllers/users/reports_controller.rb (1)

35-40: 認可チェックの重複と UI 体験の改善(HTML はリダイレクト、API は 403 に)

  • この行の head :forbidden は、直前の before_action :require_admin_or_mentor_login と認可責務が重複しています。認可はフィルタに集約し、ここでは純粋に一覧取得のみとするのがシンプルです。
  • HTML アクセス時に 403 ヘッダのみだと不親切との既出コメントに同意です。フィルタ側でフラッシュメッセージ付きリダイレクト、API など非 HTML では 403 を返すのが良いです。

差分(重複認可の削除):

-    @reports = if @target == 'unchecked_reports'
-                 head :forbidden unless current_user.admin_or_mentor?
-                 @user.reports.unchecked.not_wip.list.page(params[:page])
-               else
-                 @user.reports.list.page(params[:page])
-               end
+    @reports = if @target == 'unchecked_reports'
+                 @user.reports.unchecked.not_wip.list.page(params[:page])
+               else
+                 @user.reports.list.page(params[:page])
+               end

フィルタ実装例(このコントローラに追加。既に共通実装があればそちらに寄せる):

# private の中に追加
def require_admin_or_mentor_login
  return if current_user&.admin_or_mentor?
  respond_to do |format|
    format.html { redirect_to user_reports_path(@user, target: 'all_reports'), alert: '管理者・メンターとしてログインしてください' }
    format.json { head :forbidden }
  end
end

補足: グローバルでログイン必須なら current_user の nil は起きませんが、そうでない場合は上記のようにセーフナビゲーションで防御しておくと安全です。

#!/bin/bash
# 直近の UI 要件に合うか、HTML/JSON のレスポンス分岐箇所をざっと確認
rg -n -C2 -g 'app/controllers/**' "respond_to\\s+do\\s+\\|format\\|"

# user_reports_path の存在確認(パス名が異なる場合は適宜置換)
rg -n -C2 -g 'config/routes*.rb' "resources\\s*:users"
🧹 Nitpick comments (2)
app/controllers/users/reports_controller.rb (2)

6-6: 条件は params ではなく @target を参照して一元化を

set_target での解釈結果に依存させると、将来 params[:target] を正規化・別名対応した際も挙動がズレません。併せて、認可は before_action に集約し、業務ロジック(一覧取得)からは外すと責務が明確になります。

適用差分:

-  before_action :require_admin_or_mentor_login, if: -> { params[:target] == 'unchecked_reports' }
+  before_action :require_admin_or_mentor_login, if: -> { @target == 'unchecked_reports' }

47-49: target パラメータのホワイトリスト化(無効値は all_reports にフォールバック)

予期しない値が来ても安全側に倒すようにしておくと将来の変更に強くなります。

適用差分:

-  def set_target
-    @target = params[:target] || 'all_reports'
-  end
+  def set_target
+    allowed = %w[all_reports unchecked_reports]
+    @target = allowed.include?(params[:target]) ? params[:target] : 'all_reports'
+  end
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8c8a51b and 525bf87.

📒 Files selected for processing (3)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/javascript/components/Reports.jsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/controllers/api/reports_controller.rb
  • app/javascript/components/Reports.jsx
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/users/reports_controller.rb
🧬 Code graph analysis (1)
app/controllers/users/reports_controller.rb (2)
app/models/user.rb (1)
  • admin_or_mentor? (662-664)
app/controllers/reports_controller.rb (1)
  • set_report (106-108)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (2)
app/controllers/users/reports_controller.rb (2)

5-5: LGTM: set_target を最初に実行する順序は適切です

後続のフィルタや分岐が @target に依存するため、読みやすさ・保守性の観点でも良い並びです。


6-6: require_admin_or_mentor_login は既に定義・インクルード済みです
require_admin_or_mentor_login
app/controllers/concerns/authentication/access_requirements.rb 内の Authentication::AccessRequirements モジュールで定義されており
app/controllers/concerns/authentication.rb(7行目)で Authentication::AccessRequirements をインクルード
app/controllers/application_controller.rb(2行目)で Authentication モジュールをインクルード

の流れで ApplicationController 配下にメソッドが組み込まれているため、Users::ReportsController でも問題なく利用可能です。追加の実装作業は不要です。

@hirokiej hirokiej force-pushed the feature/add-unchecked-reports-tab-for-mentor-only branch from 525bf87 to 4ead1d8 Compare August 24, 2025 12:58
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/javascript/components/Reports.jsx (1)

27-29: practice_id の二重付与バグ解消を確認しました(userPracticeId 優先)

const pid = userPracticeId || practiceId による一本化で重複付与が解消され、レビュー指摘に対応できています。

🧹 Nitpick comments (4)
app/controllers/users/reports_controller.rb (3)

5-6: ターゲット判定は @target に統一し、メソッド化して可読性と保守性を向上させましょう

set_target で正規化した値を使うほうが意図が明確です。条件はラムダではなく述語メソッド化するとテストもしやすくなります。

適用例:

-  before_action :require_admin_or_mentor_login, if: -> { params[:target] == 'unchecked_reports' }
+  before_action :require_admin_or_mentor_login, if: :unchecked_target?
   def set_target
-    @target = params[:target] || 'all_reports'
+    allowed = %w[all_reports unchecked_reports]
+    @target = allowed.include?(params[:target]) ? params[:target] : 'all_reports'
   end
+
+  def unchecked_target?
+    @target == 'unchecked_reports'
+  end

35-39: 未チェック一覧の取得ロジックは妥当です(認可ガードと整合)

@user.reports.unchecked.not_wip.list.page(...) と通常一覧の分岐は明快で、上位の before_action による認可ガードとも整合しています。N+1 が気になる場合は必要に応じて includes(...) を検討ください(画面で参照している関連に依存)。


46-48: target のホワイトリスト化で誤指定に強く

任意文字列が来ても else 側に落ちるので致命傷ではないですが、明示的に許容値をホワイトリスト化しておくと将来の拡張や静的解析での検出がしやすくなります。上の提案と同じ差分で反映可能です。

app/javascript/components/Reports.jsx (1)

24-31: クエリの組み立ては URLSearchParams で安全・簡潔に

文字列連結は可読性とエンコードの面で脆くなりがちです。URLSearchParams に寄せると安全・明瞭になります。

-  let reportsUrl = `/api/reports.json?page=${page}`
-  if (userId) reportsUrl += `&user_id=${userId}`
-  if (companyId) reportsUrl += `&company_id=${companyId}`
-  const pid = userPracticeId || practiceId
-  if (pid) reportsUrl += `&practice_id=${pid}`
-  if (unchecked) reportsUrl += `&target=unchecked_reports`
+  const params = new URLSearchParams({ page: String(page) })
+  if (userId) params.set('user_id', String(userId))
+  if (companyId) params.set('company_id', String(companyId))
+  const pid = userPracticeId || practiceId
+  if (pid) params.set('practice_id', String(pid))
+  if (unchecked) params.set('target', 'unchecked_reports')
+  const reportsUrl = `/api/reports.json?${params.toString()}`
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 525bf87 and 4ead1d8.

📒 Files selected for processing (3)
  • app/controllers/api/reports_controller.rb (1 hunks)
  • app/controllers/users/reports_controller.rb (2 hunks)
  • app/javascript/components/Reports.jsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/api/reports_controller.rb
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

⚙️ CodeRabbit configuration file

**/*.rb: # refactoring

  • まずFat Controllerを避け、次にFat Modelを避ける。
  • Serviceクラスの乱用を避ける。
  • controller concernを作ろうとしたらPORO(Plain Old Ruby Object)やActiveRecordモデルでの実装で代替できないか検討する。

Rails Patterns

  • ViewHelperにメソッドを実装する時にはまずDecoratorパターンを使うことを検討する。(active_decorator gemを導入しているのでそれを使う)
  • 複雑なActiveRecordクエリがあり、再利用できそうな場合はQueryObjectパターンを検討する。(rails-patterns gemを導入しているのでそれのQuery機能を使う)
  • Viewにpartialを作る場合はViewComponentを使うことを検討する。
  • 複数のActiveRecordモデルを操作する1つの責務がある時や外部APIとやりとりする処理がある場合にはInteractorオブジェクトパターンを検討する。(interactor gemを導入しているのでそれを使う)
  • 複数のInteractorを実行するような処理がある場合Organizerオブジェクトパターンを検討する。(interactor gemを導入しており、その中にOrganizerの機能があるので使う)

Files:

  • app/controllers/users/reports_controller.rb
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (1)
app/javascript/components/Reports.jsx (1)

1-1: 不要な useEffect 削除に伴う import の整理、OKです

useState のみの import にできており適切です。

Copy link
Copy Markdown
Contributor Author

@hirokiej hirokiej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご確認ありがとうございます🙇

修正しましたので、再度ご確認よろしくお願いいたします!

render PageTabsComponent.new(tabs:, active_tab:)
end

def unchecked_report_badge(current_user:, user:)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに、その方がわかりやすくて良いと思いました。
修正します!


def set_reports
@reports = user.reports.list.page(params[:page])
@reports = case @target
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

caseからifを足したときにどうにかできないかな?と思ったのですが、==をを使えば、if/elseでいけましたね…!
修正します!

@reports = user.reports.list.page(params[:page])
@reports = case @target
when 'unchecked_reports'
head :forbidden unless current_user.admin_or_mentor?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別のページではどうなっているかを怠っていました💦

ユーザーフレンドリーではないかと思いました。🤔

それとは別でユーザーフレンドリーという部分の考えもなかったので勉強になりました🙇
修正します!

= user_page_tabs(@user, active_tab: '日報')

= react_component('Reports', userId: @user.id, practices: @current_user_practice.sorted_practices)
- if mentor_login?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

勝手な思い込みでadminroleとしてメンターも含まれていると勘違いしておりました。
ご確認ありがとうございます🙇
修正します!

Copy link
Copy Markdown
Contributor

@sjabcdefin sjabcdefin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

修正お疲れさまです。😌🍵
すみません。追加で1点コメントさせていただきました。
ご確認お願いいたします。😌🙏

tabs << { name: 'プロフィール', link: user_path(user) }
tabs << { name: 'ポートフォリオ', link: user_portfolio_path(user) }
tabs << { name: '日報', link: user_reports_path(user), count: user.reports.length }
tabs << { name: '日報', link: user_reports_path(user), count: user.reports.length, badge: unchecked_report_badge }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

管理者・メンターユーザでメンターモードOFF時に、未チェック数バッチが表示されているようです。
管理者・メンターユーザでメンターモードOFF時は、未チェック数バッチが表示されない仕様だったと認識していますが、認識合っていますでしょうか?
ご確認いただけると幸いです。😌
(最初のレビューの際に気づけなくて失礼いたしました。)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

提出物の部分もメンタモードオフでバッヂが表示になっていたので、同様の対応をしました💦
勝手な推測になってしまうのですが、メンターモードオフだと確認が遅れてしまうということがあるのかな?と思いました。しかし、チームリーダーに確認すべきだと思ったので、komagataさんに確認してみます。

image

@komagata さん
お疲れ様です。
こちらの件ですが、提出物のタブではメンターモードオフの場合、バッヂが表示されていますが、未確認の日報数の場合は非表示にすべきでしょうか?
お手隙でご確認よろしくお願いいたします。

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej

勝手な推測になってしまうのですが、メンターモードオフだと確認が遅れてしまうということがあるのかな?と思いました。

こちらどういった意味でしょうか?

質問に回答するのにこの情報が必要かもなので先に聞いておきたいと思いました。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata さん

質問に回答するのにこの情報が必要かもなので先に聞いておきたいと思いました。

ユーザーとしては提出物や日報がチェックされていると安心感につながる部分だと感じています。
そのため、未チェックの日報数がadmin/mentorにはデフォルトで表示されている方が、未チェックに気づきやすく対応しやすいのではないか、という推測でした。

提出物のタブではメンターモードオフでも表示されているのに、日報では非表示にするのは統一感もないと感じ、提出物のタブに合わせる形で実装しています。
しかし、どちらが適切か判断がつかなかったため、改めて確認させていただきました🙇

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej 返信ありがとうございます。

すみません、ちょっと僕の聞き方がわかりづらかったです。
僕がわからなかった部分は、

メンターモードオフだと確認が遅れてしまうということがあるのかな?

という部分です。「確認が遅れてしまう」というのはどういう意味でしょうか。
(「誰が」、「何を」、「なぜ」、遅れてしまうと考えたのでしょうか)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata さん

ご確認ありがとうございます。

「確認が遅れてしまう」というのはどういう意味でしょうか。
(「誰が」、「何を」、「なぜ」、遅れてしまうと考えたのでしょうか)

「メンターモードをオフにしているメンター」が「未チェックの日報の確認」を「メンターモードをオフにしている結果、バッヂが非表示になり認識しづらくなる」ため確認が遅れやすくなってしまうかも、という仮説として示したつもりでした。

こちらこそ言葉足らずで申し訳ございませんでした🙇💦

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej なるほどですね。説明ありがとうございます。

こちらの件ですが、提出物のタブではメンターモードオフの場合、バッヂが表示されていますが、未確認の日報数の場合は非表示にすべきでしょうか?

メンターモードがオフでも未確認の日報数は非表示にしなくてOKです。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata さん
ご回答ありがとうございます🙇

@sjabcdefin さん

メンターモードがオフでも未確認の日報数は非表示にしなくてOKです。

確認したところ、表示した状態で大丈夫とのことでした🙇

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hirokiej
ご確認ありがとうございます。😌🙏
私の方での確認は以上ですので、approveさせていただきます。🙆‍♀️

ご対応ありがとうございました!🎉

Copy link
Copy Markdown
Contributor Author

@hirokiej hirokiej Aug 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sjabcdefin さん 
ご確認、ご対応ありがとうございました🙇!

Copy link
Copy Markdown
Contributor

@sjabcdefin sjabcdefin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません!
コメントだけして、Approve失念していました。
Approveさせていただきます。🙇‍♀️

@hirokiej
Copy link
Copy Markdown
Contributor Author

hirokiej commented Sep 2, 2025

@sjabcdefin
私も確認できていませんでした💦
お知らせいただきありがとうございます🙇

@komagata さん
お疲れ様です。
メンバーレビューが終了しましたので、レビューをよろしくお願い致します🙇

Copy link
Copy Markdown
Member

@komagata komagata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確認させて頂きました。OKです〜🙆‍♂️

@komagata komagata merged commit 77a09a1 into main Sep 3, 2025
55 checks passed
@komagata komagata deleted the feature/add-unchecked-reports-tab-for-mentor-only branch September 3, 2025 20:02
@github-actions github-actions bot mentioned this pull request Sep 3, 2025
24 tasks
@hirokiej
Copy link
Copy Markdown
Contributor Author

hirokiej commented Sep 4, 2025

@komagata さん
ご確認ありがとうございます🙇

@machida
Copy link
Copy Markdown
Member

machida commented Sep 7, 2025

本番で確認できましたー🎉🎉🎉

@hirokiej
Copy link
Copy Markdown
Contributor Author

hirokiej commented Sep 7, 2025

@machida さん
ご確認ありがとうございます🙇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants