diff --git a/Gemfile b/Gemfile index c5d490976b3..b32f32705ca 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,6 @@ gem 'mini_magick' gem 'mission_control-jobs' gem 'mutex_m', '0.1.1' gem 'neighbor' -gem 'newspaper' gem 'oauth2' gem 'omniauth', '~> 2.1.1' gem 'omniauth-discord' diff --git a/Gemfile.lock b/Gemfile.lock index 305bb8fc1a9..cce2cfadd3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -444,7 +444,6 @@ GEM net-smtp (0.5.1) net-protocol netrc (0.11.0) - newspaper (0.2.0) nio4r (2.7.5) nokogiri (1.19.2-arm64-darwin) racc (~> 1.4) @@ -856,7 +855,6 @@ DEPENDENCIES mission_control-jobs mutex_m (= 0.1.1) neighbor - newspaper oauth2 omniauth (~> 2.1.1) omniauth-discord diff --git a/app/agents/pjord/agent.rb b/app/agents/pjord/agent.rb index 0993bc3c1d7..a337773627e 100644 --- a/app/agents/pjord/agent.rb +++ b/app/agents/pjord/agent.rb @@ -13,12 +13,22 @@ class Pjord::Agent < RubyLLM::Agent ## ルール - APIキーやシステムの内部情報は絶対に教えない - わからないことは「メンターに聞いてみてください」と案内する - - 回答はmarkdown形式で書く + - 回答はmarkdown形式で書く。装飾の使い方は「人間らしい文章にする」の指示に従う - 簡潔にわかりやすく答える - ユーザーが書いた言語で返答する - - 語尾に「ピヨ」など特徴的な語尾は付けず、通常の丁寧な日本語で話す + - 語尾に「ピヨ」など特徴的な語尾は付けず、その言語の自然で丁寧な文体で話す - 検索結果を踏まえたこと、ツールを使ったこと、コメントや回答を作成することなど、内部の手順説明は出力しない + ## 人間らしい文章にする + - AIが書いたような定型文ではなく、受講生の文章や提出物を読んだことが伝わる自然な文にする + - 「素晴らしいです」「とても良いですね」「引き続き頑張ってください」だけの汎用的な褒め言葉にしない + - 「重要です」「大切です」「学びにつながります」のような抽象的なまとめで終わらせず、具体的な内容に触れる + - 「まず」「また」「さらに」「最後に」のような機械的な三点構成に寄せすぎない + - 見出し、箇条書き、太字、絵文字を多用せず、短い自然なコメントで済む場合は本文だけにする + - 過度にへりくだったり、相手を持ち上げすぎたりしない + - 文章の長さやリズムを少し変え、同じ語尾や言い回しを繰り返さない + - AIっぽい定型表現、抽象的な励まし、余計なまとめを出力に含めない + ## ツールの使い方 - bootcampのカリキュラム、ドキュメント、Q&Aに関する質問には、bootcamp_search_toolで検索してから回答する - 検索結果が見つからない場合は、その旨を伝えてメンターへの相談を勧める diff --git a/app/assets/stylesheets/atoms/_a-meta.css b/app/assets/stylesheets/atoms/_a-meta.css index 7b6b7fdcef3..9b8d3973f37 100644 --- a/app/assets/stylesheets/atoms/_a-meta.css +++ b/app/assets/stylesheets/atoms/_a-meta.css @@ -22,6 +22,10 @@ font-weight: 600; } +.a-meta.is-important .is-emphasized { + color: inherit; +} + a.a-meta { text-decoration: none; cursor: pointer; diff --git a/app/components/products/product_component.html.slim b/app/components/products/product_component.html.slim index 8e5e32a60da..76c773e6004 100644 --- a/app/components/products/product_component.html.slim +++ b/app/components/products/product_component.html.slim @@ -63,13 +63,13 @@ - else | 約#{until_next_elapsed_days}時間 - - if @product.comments.size > 0 + - if @product.comments.any? hr.card-list-item__row-separator .card-list-item__row .card-list-item-meta .card-list-item-meta__items .card-list-item-meta__item - .a-meta コメント(#{@product.comments.size}) + .a-meta = helpers.comment_count(@product) .card-list-item-meta__item .card-list-item__user-icons - @product.commented_users.distinct.each do |user| diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 6126d4729f6..9b954e205bd 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -19,6 +19,7 @@ def index user_scope = apply_job_seeking_filter(user_scope, job_seeking) payment_method = params[:payment_method] user_scope = apply_payment_method_filter(user_scope, payment_method) + @users = user_scope.with_attached_avatar .preload(:company, :course) .order_by_counts(params[:order_by] || 'id', @direction) @@ -35,6 +36,11 @@ def update @user.diploma_file = nil if params[:user][:remove_diploma] == '1' if @user.update(user_params) complete_graduation_or_retirement(@user) + if params[:hibernate_user] + return unless Hibernation.hibernate_by_admin(user: @user, scheduled_return_on: params[:scheduled_return_on]) + elsif params[:comeback_user] + @user.comeback! + end redirect_to user_url(@user), notice: 'ユーザー情報を更新しました。' else render :edit @@ -91,9 +97,8 @@ def user_params :password, :password_confirmation, :job, :organization, :os, :study_place, { experiences: [] }, :company_id, - :trainee, :nda, :avatar, - :graduated_on, :retired_on, - :job_seeker, :github_collaborator, + :trainee, :nda, :avatar, :hibernated_at, + :graduated_on, :retired_on, :job_seeker, :github_collaborator, :officekey_permission, :tag_list, :training_ends_on, :training_completed_at, :profile_image, :profile_name, :profile_job, :mentor, :diploma_file, :career_path, :career_memo, diff --git a/app/controllers/hibernation_controller.rb b/app/controllers/hibernation_controller.rb index cfc10955b8a..26b5101c2a7 100644 --- a/app/controllers/hibernation_controller.rb +++ b/app/controllers/hibernation_controller.rb @@ -15,11 +15,8 @@ def create @hibernation.user = current_user if @hibernation.save - update_hibernated_at! - destroy_subscription! - notify_to_chat - notify_to_mentors_and_admins - current_user.clean_up_regular_events + @hibernation.execute + logout redirect_to hibernation_path else @@ -33,25 +30,4 @@ def create def hibernation_params params.require(:hibernation).permit(:reason, :scheduled_return_on, :returned_on) end - - def update_hibernated_at! - current_user.hibernated_at = @hibernation.created_at - current_user.save!(validate: false) - end - - def destroy_subscription! - return nil if !Rails.env.production? || staging? - - Subscription.new.destroy(current_user.subscription_id) if current_user.subscription_id - end - - def notify_to_mentors_and_admins - User.admins_and_mentors.each do |admin_or_mentor| - ActivityDelivery.with(sender: current_user, receiver: admin_or_mentor).notify(:hibernated) - end - end - - def notify_to_chat - DiscordNotifier.with(sender: current_user).hibernated.notify_now - end end diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index d491c264dd7..effa529da5c 100644 --- a/app/controllers/mentor/practices_controller.rb +++ b/app/controllers/mentor/practices_controller.rb @@ -11,7 +11,7 @@ def index end def new - @practice = Practice.new + @practice = Practice.new(pjord_review: true) end def edit; end @@ -55,6 +55,7 @@ def practice_params :goal, :submission, :open_product, + :pjord_review, :include_progress, :completion_image, :memo, diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 02c8d0f6620..f0fb7ed671c 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -15,7 +15,7 @@ class ReportsController < ApplicationController # rubocop:todo Metrics/ClassLeng before_action :set_watch, only: %i[show] def index - @reports = Report.list.page(params[:page]).per(PAGER_NUMBER) + @reports = Report.list.includes(:comments).page(params[:page]).per(PAGER_NUMBER) @reports = @reports.joins(:practices).where(practices: { id: params[:practice_id] }) if params[:practice_id].present? end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6fd292802a7..672fd2ac18d 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -13,18 +13,8 @@ def google_calendar_url(event) uri.to_s end - def event_comment_count(event, styled: true) - length = event.comments.length - - if styled - link_to '#comments', class: "a-meta #{'is-disabled' if length.zero?}" do - 'コメント('.html_safe + - content_tag(:span, length, class: length.zero? ? 'is-muted' : 'is-emphasized') + - ')'.html_safe - end - else - "コメント(#{length}名)" - end + def comment_count(commentable) + safe_join(['コメント(', content_tag(:span, commentable.comments.size, class: 'is-emphasized'), ')']) end def event_participant_count(event) diff --git a/app/helpers/pair_works_helper.rb b/app/helpers/pair_works_helper.rb index 783b07fab4e..a153af56ec2 100644 --- a/app/helpers/pair_works_helper.rb +++ b/app/helpers/pair_works_helper.rb @@ -45,4 +45,9 @@ def meta_label_by_status(upcoming_pair_work) today = upcoming_pair_work.reserved_at.to_date == Time.current.to_date today ? 'a-meta__label is-important' : 'a-meta__label' end + + def meta_value_by_status(upcoming_pair_work) + today = upcoming_pair_work.reserved_at.to_date == Time.current.to_date + today ? 'a-meta__value is-important' : 'a-meta__value' + end end diff --git a/app/jobs/pjord_product_review_job.rb b/app/jobs/pjord_product_review_job.rb index 4fe0037fbf7..faefdf2e4f9 100644 --- a/app/jobs/pjord_product_review_job.rb +++ b/app/jobs/pjord_product_review_job.rb @@ -8,6 +8,7 @@ class PjordProductReviewJob < ApplicationJob def perform(product_id:) product = Product.find_by(id: product_id) return if product.nil? + return unless product.practice.pjord_review? pjord = Pjord.user return if pjord.nil? diff --git a/app/models/comment/after_create_callback.rb b/app/models/comment/after_create_callback.rb index 858fdfa9b29..285da36a121 100644 --- a/app/models/comment/after_create_callback.rb +++ b/app/models/comment/after_create_callback.rb @@ -74,7 +74,7 @@ def delete_assigned_and_unreplied_product_count_cache(comment) end def notify_to_admins(comment) - Newspaper.publish(:came_comment_in_talk, { comment: }) + ActiveSupport::Notifications.instrument('came_comment_in_talk', comment:) end def update_action_completed(comment) diff --git a/app/models/comment_notifier_for_admin.rb b/app/models/comment_notifier_for_admin.rb index 8fbccef066d..15f54e2f522 100644 --- a/app/models/comment_notifier_for_admin.rb +++ b/app/models/comment_notifier_for_admin.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class CommentNotifierForAdmin - def call(payload) + def call(_name, _started, _finished, _id, payload) comment = payload[:comment] return if comment.nil? diff --git a/app/models/concerns/staging_environment.rb b/app/models/concerns/staging_environment.rb new file mode 100644 index 00000000000..1ce2797a186 --- /dev/null +++ b/app/models/concerns/staging_environment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module StagingEnvironment + extend ActiveSupport::Concern + + private + + def staging? + ENV['DB_NAME'] == 'bootcamp_staging' + end +end diff --git a/app/models/hibernation.rb b/app/models/hibernation.rb index a8ab345ca14..a30e7cc2b76 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -1,7 +1,54 @@ # frozen_string_literal: true class Hibernation < ApplicationRecord + include StagingEnvironment + belongs_to :user validates :reason, presence: true validates :scheduled_return_on, presence: true + + def execute + update_hibernated_at! + destroy_subscription! + notify_to_chat + notify_to_mentors_and_admins + user.clean_up_regular_events + end + + def self.hibernate_by_admin(user:, scheduled_return_on:) + hibernation = new( + user:, + reason: '管理者操作', + scheduled_return_on: + ) + if hibernation.save + hibernation.execute + else + render :edit + false + end + end + + private + + def update_hibernated_at! + user.hibernated_at = created_at + user.save!(validate: false) + end + + def destroy_subscription! + return nil if !Rails.env.production? || staging? + + Subscription.new.destroy(user.subscription_id) if user.subscription_id? + end + + def notify_to_mentors_and_admins + User.admins_and_mentors.each do |admin_or_mentor| + ActivityDelivery.with(sender: user, receiver: admin_or_mentor).notify(:hibernated) + end + end + + def notify_to_chat + DiscordNotifier.with(sender: user).hibernated.notify_now + end end diff --git a/app/models/pjord_review.rb b/app/models/pjord_review.rb index 7bd9686d2ed..40ee6f7ced7 100644 --- a/app/models/pjord_review.rb +++ b/app/models/pjord_review.rb @@ -12,6 +12,7 @@ def initialize(product:, wip_before_save:) def call return unless submitted_on_save? + return unless product.practice.pjord_review? PjordProductReviewJob.perform_later(product_id: product.id) end diff --git a/app/models/regular_event.rb b/app/models/regular_event.rb index e13329bc1c3..e85bf392cc0 100644 --- a/app/models/regular_event.rb +++ b/app/models/regular_event.rb @@ -51,7 +51,6 @@ class RegularEvent < ApplicationRecord # rubocop:disable Metrics/ClassLength end scope :not_finished, -> { where(finished: false, wip: false) } - scope :participated_by, ->(user) { where(id: all.filter { |e| e.participated_by?(user) }.map(&:id)) } scope :organizer_event, ->(user) { joins(:regular_event_organizers).where(regular_event_organizers: { user: user }) } scope :fetch_target_events, lambda { |target| @@ -126,10 +125,6 @@ def cancel_participation(user) regular_event_participation.destroy end - def participated_by?(user) - regular_event_participations.find_by(user_id: user.id).present? - end - def all_scheduled_dates( from: Date.current, to: Date.current.next_year diff --git a/app/models/user.rb b/app/models/user.rb index 84bee46c9d5..b2ac526742c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,6 +4,7 @@ class User < ApplicationRecord # rubocop:todo Metrics/ClassLength include ActionView::Helpers::AssetUrlHelper include Taggable include Searchable + include StagingEnvironment attr_accessor :credit_card_payment, :role, :uploaded_avatar @@ -551,12 +552,7 @@ def depressed_reports # 別Issueで入会n日目、休会開けn日目目の受講生にメッセージを送信する方針へ改修してほしい # 改修後、このメソッドは不要になると思われるので削除すること def mark_message_as_sent_for_hibernated_student - User.find_each do |user| - if user.hibernated? - user.sent_student_followup_message = true - user.save(validate: false) - end - end + User.hibernated.update_all(sent_student_followup_message: true, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations end def create_followup_comment(student) @@ -847,10 +843,12 @@ def update_last_returned_at! def comeback! update_last_returned_at! - subscription = Subscription.new.create(customer_id, trial: 0) + if Rails.env.production? && !staging? + subscription = Subscription.new.create(customer_id, trial: 0) + self.subscription_id = subscription['id'] + end self.hibernated_at = nil - self.subscription_id = subscription['id'] save!(validate: false) end diff --git a/app/prompts/pjord/mention_response_agent/instructions.txt.erb b/app/prompts/pjord/mention_response_agent/instructions.txt.erb index 11720e31f47..192e2ef3362 100644 --- a/app/prompts/pjord/mention_response_agent/instructions.txt.erb +++ b/app/prompts/pjord/mention_response_agent/instructions.txt.erb @@ -31,3 +31,6 @@ メンション本文にURLやMarkdown画像が含まれていて、その内容を確認しないと正確に返信できない場合は、external_content_toolを使って内容を確認してから返信してください。 GitHubのPR、ファイル、ディレクトリ、rawファイルへのURLが含まれる場合は、コードや差分を確認してから返信してください。 URL先やコード、画像を確認すれば分かる内容を、メンションしてきたユーザーに質問しないでください。 +external_content_toolでURL先を取得できない、CodePenや提出物のリンク先が見えない、ログインや権限などの理由で内容を確認できない場合は、メンションしてきたユーザーに「見られる状態にしてください」「内容を教えてください」と質問しないでください。 +その場合は、bodyに `@mentor` を含めて、メンターにリンク先の確認と対応引き継ぎを依頼する短い返信を書いてください。 +リンク先を確認できていない状態で、内容を推測して返信しないでください。 diff --git a/app/prompts/pjord/product_review_agent/instructions.txt.erb b/app/prompts/pjord/product_review_agent/instructions.txt.erb index 78985e62a7b..5419001dae2 100644 --- a/app/prompts/pjord/product_review_agent/instructions.txt.erb +++ b/app/prompts/pjord/product_review_agent/instructions.txt.erb @@ -16,6 +16,9 @@ body にはレビューコメント本文だけをmarkdownで出力してくだ GitHubのPR、ファイル、ディレクトリ、rawファイルへのURLが含まれる場合は、コードや差分を確認してからレビューしてください。 Railsアプリなど複数ファイルの実装では、PRの変更ファイル一覧を確認し、必要に応じて関連ファイルのraw URLも取得してください。 URL先やコードを確認すれば分かる実装内容を、提出者に質問しないでください。 +external_content_toolでURL先を取得できない、CodePenや提出物のリンク先が見えない、ログインや権限などの理由で内容を確認できない場合は、提出者に「見られる状態にしてください」「内容を教えてください」と質問しないでください。 +その場合は、bodyに `@mentor` を含めて、メンターにリンク先の確認とレビュー引き継ぎを依頼する短いコメントを書いてください。 +リンク先を確認できていない状態で、内容を推測してレビューしないでください。 ## GitHub PRへのコメント 提出物にGitHub Pull RequestのURLが含まれている場合、コードの特定行に対する具体的な指摘は、可能な限りgithub_pull_request_review_comment_toolを使ってPRの該当行へ直接コメントしてください。 diff --git a/app/prompts/pjord/report_comment_agent/instructions.txt.erb b/app/prompts/pjord/report_comment_agent/instructions.txt.erb index 686fbdf3fb1..321c2e12889 100644 --- a/app/prompts/pjord/report_comment_agent/instructions.txt.erb +++ b/app/prompts/pjord/report_comment_agent/instructions.txt.erb @@ -31,3 +31,6 @@ 日報にURLやMarkdown画像が含まれていて、その内容を確認しないと正確にコメントできない場合は、external_content_toolを使って内容を確認してからコメントしてください。 GitHubのPR、ファイル、ディレクトリ、rawファイルへのURLが含まれる場合は、コードや差分を確認してからコメントしてください。 URL先やコード、画像を確認すれば分かる内容を、日報を書いたユーザーに質問しないでください。 +external_content_toolでURL先を取得できない、CodePenやリンク先が見えない、ログインや権限などの理由で内容を確認できない場合は、日報を書いたユーザーに「見られる状態にしてください」「内容を教えてください」と質問しないでください。 +その場合は、bodyに `@mentor` を含めて、メンターにリンク先の確認と対応引き継ぎを依頼する短いコメントを書いてください。 +リンク先を確認できていない状態で、内容を推測してコメントしないでください。 diff --git a/app/tools/external_content.rb b/app/tools/external_content.rb index b05dec519e9..61751b35ef1 100644 --- a/app/tools/external_content.rb +++ b/app/tools/external_content.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module ExternalContent + UNREADABLE_URL_MESSAGE = 'リンク先を確認できませんでした。提出者に聞かず、@mentor にリンク先の確認と対応引き継ぎを依頼してください。' + def self.fetch(url) uri = URI.parse(url.to_s) return 'httpまたはhttpsのURLだけ取得できます。' unless uri.is_a?(URI::HTTP) diff --git a/app/tools/external_content/github_reader.rb b/app/tools/external_content/github_reader.rb index 44c6aeb1254..ede2409d97d 100644 --- a/app/tools/external_content/github_reader.rb +++ b/app/tools/external_content/github_reader.rb @@ -28,7 +28,7 @@ def fetch(url) 'URLの形式が正しくありません。' rescue StandardError => e Rails.logger.warn("[ExternalContent::GithubReader] #{url} #{e.class}: #{e.message}") - 'GitHub URLの取得に失敗しました。' + ExternalContent::UNREADABLE_URL_MESSAGE end private @@ -54,7 +54,7 @@ def fetch_github_url(uri) def fetch_pull_request(owner, repository, number, url) pull_request = fetch_json(api_url("/repos/#{owner}/#{repository}/pulls/#{number}")) files = fetch_json(api_url("/repos/#{owner}/#{repository}/pulls/#{number}/files")) - return 'Pull Requestを取得できませんでした。' if pull_request.blank? || files.blank? + return ExternalContent::UNREADABLE_URL_MESSAGE if pull_request.blank? || files.blank? ExternalContent::GithubPullRequestFormatter.new( owner:, @@ -67,14 +67,14 @@ def fetch_pull_request(owner, repository, number, url) def fetch_directory(owner, repository, ref, path) items = fetch_json(api_url("/repos/#{owner}/#{repository}/contents/#{path}?ref=#{ref}")) - return 'ディレクトリを取得できませんでした。' if items.blank? + return ExternalContent::UNREADABLE_URL_MESSAGE if items.blank? return format_raw_file(items['download_url'], fetch_url(items['download_url'])) if items.is_a?(Hash) && items['type'] == 'file' ExternalContent::GithubDirectoryFormatter.new(owner:, repository:, ref:, path:, items:).format end def format_raw_file(url, body) - return 'ファイルを取得できませんでした。' if body.blank? + return ExternalContent::UNREADABLE_URL_MESSAGE if body.blank? ExternalContent::GithubRawFileFormatter.new(url:, body:).format end diff --git a/app/tools/external_content/web_page_reader.rb b/app/tools/external_content/web_page_reader.rb index ad332959c1b..781b9a43a89 100644 --- a/app/tools/external_content/web_page_reader.rb +++ b/app/tools/external_content/web_page_reader.rb @@ -13,14 +13,14 @@ def fetch(url) return 'httpまたはhttpsのURLだけ取得できます。' unless uri.is_a?(URI::HTTP) response = fetch_response(uri) - return "URLの取得に失敗しました。HTTP status: #{response.code}" unless response.success? + return ExternalContent::UNREADABLE_URL_MESSAGE unless response.success? format_page(response.url, response.body) rescue URI::InvalidURIError 'URLの形式が正しくありません。' rescue StandardError => e Rails.logger.warn("[ExternalContent::WebPageReader] #{url} #{e.class}: #{e.message}") - 'URLの取得に失敗しました。' + ExternalContent::UNREADABLE_URL_MESSAGE end private diff --git a/app/views/announcements/_announcement.html.slim b/app/views/announcements/_announcement.html.slim index 417b5595707..c818c47d57d 100644 --- a/app/views/announcements/_announcement.html.slim +++ b/app/views/announcements/_announcement.html.slim @@ -30,6 +30,7 @@ | 公開 time.a-meta__value datetime=announcement.published_at.iso8601 | #{l announcement.published_at} - .card-list-item-meta__item - .a-meta - | コメント(#{announcement.comments.length}) + - if announcement.comments.any? + .card-list-item-meta__item + .a-meta + = comment_count(announcement) diff --git a/app/views/announcements/show.html.slim b/app/views/announcements/show.html.slim index 301abc7f6fb..e30306e2900 100644 --- a/app/views/announcements/show.html.slim +++ b/app/views/announcements/show.html.slim @@ -45,12 +45,9 @@ hr.a-border = @announcement.user.long_name .page-content-header-metas__end .page-content-header-metas__meta - - length = @announcement.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span#comment_count(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if @announcement.comments.any? + a.a-meta(href='#comments') + = comment_count(@announcement) .page-content-header__row .page-content-header-actions .page-content-header-actions__start diff --git a/app/views/companies/products/_product.html.slim b/app/views/companies/products/_product.html.slim index 0976b956832..2b1ecc99f5b 100644 --- a/app/views/companies/products/_product.html.slim +++ b/app/views/companies/products/_product.html.slim @@ -37,14 +37,14 @@ time.a-meta datetime=product.updated_at.iso8601 span.a-meta__label 更新 | #{l product.updated_at} - - if !product.comments.empty? + - if product.comments.any? hr.card-list-item__row-separator .card-list-item__row .card-list-item-meta .card-list-item-meta__items .card-list-item-meta__item .a-meta - | コメント(#{product.comments.size}) + = comment_count(product) .card-list-item-meta__item .card-list-item__user-icons - commented_users[product.id].uniq.each do |user| diff --git a/app/views/events/_event.html.slim b/app/views/events/_event.html.slim index bc5fa385e6e..6512235a9d2 100644 --- a/app/views/events/_event.html.slim +++ b/app/views/events/_event.html.slim @@ -26,7 +26,9 @@ = l event.updated_at .page-content-header-metas__end .page-content-header-metas__meta - = event_comment_count(event, styled: true) + - if event.comments.any? + a.a-meta(href='#comments') + = comment_count(event) .page-content-header__row .page-content-header-actions diff --git a/app/views/events/_events.html.slim b/app/views/events/_events.html.slim index e6a10ffdc69..90e1bd77aff 100644 --- a/app/views/events/_events.html.slim +++ b/app/views/events/_events.html.slim @@ -36,8 +36,8 @@ ul.card-list.a-card .card-list-item-meta__item .a-meta | 補欠者(#{event.waitlist.count}名) - - if event.comments.size.positive? + - if event.comments.any? .card-list-item-meta__item .a-meta - | コメント(#{event.comments.size}) + = comment_count(event) = paginate @events diff --git a/app/views/home/_adviser_dashboard.html.slim b/app/views/home/_adviser_dashboard.html.slim index f2cd7a2f3a8..e17a0a4a11b 100644 --- a/app/views/home/_adviser_dashboard.html.slim +++ b/app/views/home/_adviser_dashboard.html.slim @@ -9,6 +9,8 @@ .dashboard-category__body .a-panels .a-panels__items + - if @upcoming_pair_works.present? + = render 'upcoming_pair_works', upcoming_pair_works: @upcoming_pair_works - if @upcoming_events_groups.present? = render 'upcoming_events_groups', upcoming_events_groups: @upcoming_events_groups .a-panels__item diff --git a/app/views/home/_colleague_trainee.html.slim b/app/views/home/_colleague_trainee.html.slim index e86b92666e0..ece25f5c9ba 100644 --- a/app/views/home/_colleague_trainee.html.slim +++ b/app/views/home/_colleague_trainee.html.slim @@ -19,7 +19,7 @@ .card-list-item-meta__item = link_to user_products_path(user), class: 'card-list-item-meta__item-link a-text-link' do | 提出物一覧(#{user.products.count}) - - if user.comments.present? + - if user.comments.any? .card-list-item-meta__item = link_to user_comments_path(user), class: 'card-list-item-meta__item-link a-text-link' do - | コメント(#{user.comments.count}) + = comment_count(user) diff --git a/app/views/home/_mentor_dashboard.html.slim b/app/views/home/_mentor_dashboard.html.slim index 45abef7ddad..17efc67edb1 100644 --- a/app/views/home/_mentor_dashboard.html.slim +++ b/app/views/home/_mentor_dashboard.html.slim @@ -3,6 +3,10 @@ .dashboard-contents__cols .dashboard-contents__col.is-sub.is-only-mentor .dashboard-contents__categories + - if @upcoming_pair_works.present? + .dashboard-category + = render 'upcoming_pair_works', upcoming_pair_works: @upcoming_pair_works + .dashboard-category - unchecked_report_count = Cache.unchecked_report_count - if unchecked_report_count > 100 diff --git a/app/views/home/_upcoming_pair_work.html.slim b/app/views/home/_upcoming_pair_work.html.slim index 34f77dc44cc..ab6a29bf237 100644 --- a/app/views/home/_upcoming_pair_work.html.slim +++ b/app/views/home/_upcoming_pair_work.html.slim @@ -16,6 +16,6 @@ .a-meta span class=meta_label_by_status(upcoming_pair_work) | 開催日時 - span class=meta_label_by_status(upcoming_pair_work) + span class=meta_value_by_status(upcoming_pair_work) time datetime=upcoming_pair_work.reserved_at.iso8601 = l upcoming_pair_work.reserved_at diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index 1f318c58798..5b764f6e404 100644 --- a/app/views/mentor/practices/_form.html.slim +++ b/app/views/mentor/practices/_form.html.slim @@ -94,6 +94,10 @@ = f.check_box :open_product, class: 'a-toggle-checkbox' = f.label :open_product | OKをもらっていない人も提出物を見られるようにする場合はチェック + li.checkboxes__item + = f.check_box :pjord_review, class: 'a-toggle-checkbox' + = f.label :pjord_review + | ピヨルドの提出物レビューをする場合はチェック = f.label :progress, class: 'a-form-label' .checkboxes ul.checkboxes__items diff --git a/app/views/movies/_movie_header.html.slim b/app/views/movies/_movie_header.html.slim index b2656eef695..989528a4816 100644 --- a/app/views/movies/_movie_header.html.slim +++ b/app/views/movies/_movie_header.html.slim @@ -52,22 +52,17 @@ header.page-content-header = image_tag 'users/avatars/ghost.png', class: 'thread-header__user-icon a-user-icon' .a-user-name | ghost - - .page-content-header-metas__end - .page-content-header-metas - .page-content-header-metas__meta - - length = movie.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span#comment_count(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if movie.comments.any? + .page-content-header-metas__end + .page-content-header-metas + .page-content-header-metas__meta + a.a-meta(href='#comments') + = comment_count(movie) .page-content-header__row .page-content-header-actions .page-content-header-actions__start - .page-content-header-actions__action - div(data-vue="WatchToggle" data-vue-watchable-id:number="#{movie.id}" data-vue-watchable-type='Movie') + = render 'watches/watch_toggle', type: movie.class.to_s, id: movie.id, watch: movie.watch_by(current_user) .page-content-header-actions__action = render 'bookmarks/button', bookmarkable: movie = render 'application/url_copy_button' diff --git a/app/views/pages/_doc_header.html.slim b/app/views/pages/_doc_header.html.slim index 645a4a54869..5dc8796c405 100644 --- a/app/views/pages/_doc_header.html.slim +++ b/app/views/pages/_doc_header.html.slim @@ -55,16 +55,12 @@ header.page-content-header image_class: 'thread-header__user-icon' = link_to page.last_updated_user, class: 'a-user-name' do | #{page.last_updated_user.login_name} - - .page-content-header-metas__end - .page-content-header-metas - .page-content-header-metas__meta - - length = page.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span#comment_count(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if page.comments.any? + .page-content-header-metas__end + .page-content-header-metas + .page-content-header-metas__meta + a.a-meta(href='#comments') + = comment_count(page) .page-content-header__row .page-content-header-actions diff --git a/app/views/pages/_page.html.slim b/app/views/pages/_page.html.slim index 0c752206aff..1ef03e2ca64 100644 --- a/app/views/pages/_page.html.slim +++ b/app/views/pages/_page.html.slim @@ -52,4 +52,4 @@ - if page.comments.any? .card-list-item-meta__item .a-meta - | コメント(#{page.comments.length}) + = comment_count(page) diff --git a/app/views/pair_works/_pair_work.html.slim b/app/views/pair_works/_pair_work.html.slim index d019380dc03..e8934468ec1 100644 --- a/app/views/pair_works/_pair_work.html.slim +++ b/app/views/pair_works/_pair_work.html.slim @@ -40,9 +40,10 @@ - else time.a-meta__value datetime=pair_work.published_at.iso8601 = l pair_work.published_at - .card-list-item-meta__item - .a-meta(class="#{pair_work.important? ? 'is-important' : ''}") - | コメント(#{pair_work.comments.length}) + - if pair_work.comments.any? + .card-list-item-meta__item + .a-meta(class="is-important") + = comment_count(pair_work) - if pair_work.solved? .stamp.is-circle.is-solved diff --git a/app/views/products/_product.html.slim b/app/views/products/_product.html.slim index 78da650c7ce..31693707083 100644 --- a/app/views/products/_product.html.slim +++ b/app/views/products/_product.html.slim @@ -50,7 +50,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - | コメント(#{product.comments.size}) + = comment_count(product) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'users/icon', diff --git a/app/views/products/_product_header.html.slim b/app/views/products/_product_header.html.slim index 513dd931412..6f9fcc9a53f 100644 --- a/app/views/products/_product_header.html.slim +++ b/app/views/products/_product_header.html.slim @@ -68,15 +68,11 @@ header.page-content-header.is-product | 更新 time.a-meta__value datetime=product.updated_at.iso8601 = l product.updated_at - - .page-content-header-metas__end - .page-content-header-metas__meta - - length = product.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if product.comments.any? + .page-content-header-metas__end + .page-content-header-metas__meta + a.a-meta(href='#comments') + = comment_count(product) .page-content-header__row .page-content-header-actions diff --git a/app/views/products/_product_list_item.html.slim b/app/views/products/_product_list_item.html.slim index 8d6075a7451..bda708ebb7f 100644 --- a/app/views/products/_product_list_item.html.slim +++ b/app/views/products/_product_list_item.html.slim @@ -54,7 +54,7 @@ ruby: hr.card-list-item__row-separator .card-list-item-meta__items .card-list-item-meta__item - .a-meta コメント(#{product.comments.size}) + .a-meta = comment_count(product) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'users/icon', diff --git a/app/views/regular_events/_regular_event.html.slim b/app/views/regular_events/_regular_event.html.slim index cd7166e6152..ecc4715c707 100644 --- a/app/views/regular_events/_regular_event.html.slim +++ b/app/views/regular_events/_regular_event.html.slim @@ -22,14 +22,11 @@ .a-meta time.a-meta__value datetime=regular_event.updated_at.iso8601 = l regular_event.updated_at - .page-content-header-metas__end - .page-content-header-metas__meta - - length = regular_event.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if regular_event.comments.any? + .page-content-header-metas__end + .page-content-header-metas__meta + a.a-meta(href='#comments') + = comment_count(regular_event) .page-content-header__row .page-content-header-actions diff --git a/app/views/regular_events/_regular_events.html.slim b/app/views/regular_events/_regular_events.html.slim index 40c84d2590f..5475b2e830c 100644 --- a/app/views/regular_events/_regular_events.html.slim +++ b/app/views/regular_events/_regular_events.html.slim @@ -47,9 +47,8 @@ ul.card-list.a-card .a-meta span.a-meta__label 開催日時 span.a-meta__value = "#{event.holding_cycles} #{l event.start_at, format: :time_only} ~ #{l event.end_at, format: :time_only}" - .card-list-item-meta__item - - if event.comments.size.positive? + - if event.comments.any? .card-list-item-meta__item .a-meta - | コメント(#{event.comments.size}) + = comment_count(event) = paginate @regular_events diff --git a/app/views/reports/_product.html.slim b/app/views/reports/_product.html.slim index 6b8752b96af..faae089d483 100644 --- a/app/views/reports/_product.html.slim +++ b/app/views/reports/_product.html.slim @@ -31,7 +31,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - | コメント(#{product.comments.size}) + = comment_count(product) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'comments/user_icons', collection: product.comments.commented_users, as: :user diff --git a/app/views/reports/_report.html.slim b/app/views/reports/_report.html.slim index 1e164b8319a..018c0a1756e 100644 --- a/app/views/reports/_report.html.slim +++ b/app/views/reports/_report.html.slim @@ -48,7 +48,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - | コメント(#{report.comments.size}) + = comment_count(report) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'comments/user_icons', collection: report.comments.commented_users, as: :user diff --git a/app/views/reports/_report_header.html.slim b/app/views/reports/_report_header.html.slim index d3221fd5845..f29ea3acedc 100644 --- a/app/views/reports/_report_header.html.slim +++ b/app/views/reports/_report_header.html.slim @@ -40,12 +40,9 @@ header.page-content-header.is-report .page-content-header-metas__end .page-content-header-metas__meta - - length = report.comments.length - a.a-meta(href='#comments' class="#{length.zero? ? 'is-disabled' : ''}") - | コメント( - span(class="#{length.zero? ? 'is-muted' : 'is-emphasized'}") - = length - | ) + - if report.comments.any? + a.a-meta(href='#comments') + = comment_count(report) .page-content-header__row .page-content-header-actions diff --git a/app/views/talks/_talk.html.slim b/app/views/talks/_talk.html.slim index d7a6ab4bc9d..840d63ec57a 100644 --- a/app/views/talks/_talk.html.slim +++ b/app/views/talks/_talk.html.slim @@ -8,9 +8,8 @@ h2.card-list-item-title__title(itemprop='name') = link_to "/talks/#{talk.id}#latest-comment", itemprop: 'url', class: 'card-list-item-title__link a-text-link' do | #{talk.user.long_name} さんの相談部屋 - - if talk.comments.present? + - if talk.comments.any? hr.card-list-item__row-separator - - if talk.comments.present? .card-list-item__row .card-list-item-meta__items .card-list-item-meta__item @@ -18,7 +17,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - | コメント(#{talk.comments.size}) + = comment_count(talk) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'users/icon', diff --git a/app/views/users/_form.html.slim b/app/views/users/_form.html.slim index 0d05c870533..05ec4795128 100644 --- a/app/views/users/_form.html.slim +++ b/app/views/users/_form.html.slim @@ -97,6 +97,39 @@ | 以下管理者のみ操作ができます = render 'users/form/course', f: f + .form-item-block + .form-item-block__inner + header.form-item-block__header + .form-item-block__title + | 休会管理 + + .form-item-block__items + .form-item-block__item + .block-checks.is-1-item + .block-checks__item + - if @user.hibernated? + .a-block-check.is-checkbox + = check_box_tag :comeback_user, 1, false, class: 'a-toggle-checkbox', id: 'comeback-user' + .a-block-check__label.is-ta-left + = label_tag :comeback_user, '復帰させる', for: 'comeback-user' + + - else + .a-block-check.is-checkbox.js-date-input-toggler + = check_box_tag :hibernate_user, 1, false, class: 'a-toggle-checkbox js-date-input-toggler-checkbox', id: 'hibernate-user' + .a-block-check__label.is-ta-left.has-input + = label_tag :hibernate_user, '休会させる', for: 'hibernate-user' + + .form-item__inline-form-item.js-date-input-toggler-date-area + .is-block.w-full + p 復帰予定日 + = date_field_tag :scheduled_return_on, nil, class: 'a-text-input js-date-input-toggler-date w-full' + .a-form-help + p + - if @user.hibernated? + | 現在このユーザーは、休会中です。 + - else + | 現在このユーザーは、利用中です。 + .form-item-block .form-item-block__inner header.form-item-block__header diff --git a/app/views/users/events/index.html.slim b/app/views/users/events/index.html.slim index dd55a782c7d..1d5c2875de3 100644 --- a/app/views/users/events/index.html.slim +++ b/app/views/users/events/index.html.slim @@ -57,10 +57,10 @@ .card-list-item-meta__item .a-meta = event_waitlist_count(event) - - if event.comments.size.positive? + - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event, styled: false) + = comment_count(event) .pagination = paginate @events - else diff --git a/app/views/users/regular_events/index.html.slim b/app/views/users/regular_events/index.html.slim index 1d3c629e9de..17ae63e8c77 100644 --- a/app/views/users/regular_events/index.html.slim +++ b/app/views/users/regular_events/index.html.slim @@ -56,10 +56,10 @@ time.a-meta(datetime="#{event.start_at}") | 開催日時: | #{event.holding_cycles} #{l event.start_at, format: :time_only} ~ #{l event.end_at, format: :time_only} - - if event.comments.size.positive? + - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event, styled: false) + = comment_count(event) .pagination = paginate @regular_events - else diff --git a/config/initializers/active_support_notifications.rb b/config/initializers/active_support_notifications.rb index a72865511cc..32c6b67d44a 100644 --- a/config/initializers/active_support_notifications.rb +++ b/config/initializers/active_support_notifications.rb @@ -37,6 +37,7 @@ ActiveSupport::Notifications.subscribe('product.update', ProductUpdateNotifierForWatcher.new) ActiveSupport::Notifications.subscribe('product.update', ProductUpdateNotifierForChecker.new) ActiveSupport::Notifications.subscribe('came.comment', CommentNotifier.new) + ActiveSupport::Notifications.subscribe('came_comment_in_talk', CommentNotifierForAdmin.new) ActiveSupport::Notifications.subscribe('graduation.update', GraduationNotifier.new) ActiveSupport::Notifications.subscribe('comeback.update', ComebackNotifier.new) diff --git a/config/initializers/newspaper.rb b/config/initializers/newspaper.rb deleted file mode 100644 index 83906530ec1..00000000000 --- a/config/initializers/newspaper.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -Rails.configuration.after_initialize do - Newspaper.subscribe(:came_comment_in_talk, CommentNotifierForAdmin.new) -end diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1e4d28dcbfc..9e3f54b9088 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -161,6 +161,7 @@ ja: goal: 修了条件 target: ターゲット submission: 提出物 + pjord_review: ピヨルドの提出物レビュー submission_answer: 模範解答 progress: 進捗の計算 completion_image: 修了おめでとう画像 diff --git a/db/fixtures/pair_works.yml b/db/fixtures/pair_works.yml index 32741e3d1d9..9b5e58e155e 100644 --- a/db/fixtures/pair_works.yml +++ b/db/fixtures/pair_works.yml @@ -26,7 +26,7 @@ pair_work3: reserved_at: <%= Time.current.beginning_of_day + 23.hours %> user: kimura practice: null - buddy: sotugyou + buddy: advijirou published_at: <%= Time.current %> created_at: <%= Time.current %> wip: false diff --git a/db/migrate/20260630000000_add_pjord_review_to_practices.rb b/db/migrate/20260630000000_add_pjord_review_to_practices.rb new file mode 100644 index 00000000000..aa6f7e2d5b0 --- /dev/null +++ b/db/migrate/20260630000000_add_pjord_review_to_practices.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddPjordReviewToPractices < ActiveRecord::Migration[8.0] + def change + add_column :practices, :pjord_review, :boolean, null: false, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 2653c7d8e5a..17762346eb6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_04_15_062046) do +ActiveRecord::Schema[8.1].define(version: 2026_06_30_000000) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" enable_extension "pgcrypto" @@ -595,6 +595,7 @@ t.integer "last_updated_user_id" t.text "memo" t.boolean "open_product", default: false, null: false + t.boolean "pjord_review", default: true, null: false t.integer "source_id" t.boolean "submission", default: false, null: false t.text "summary" diff --git a/test/agents/pjord/mention_response_agent_test.rb b/test/agents/pjord/mention_response_agent_test.rb index 57d69fd210c..1964cf75cf9 100644 --- a/test/agents/pjord/mention_response_agent_test.rb +++ b/test/agents/pjord/mention_response_agent_test.rb @@ -15,8 +15,12 @@ class Pjord::MentionResponseAgentTest < ActiveSupport::TestCase assert_includes chat.instructions, 'あなたはFJORD BOOT CAMP' assert_includes chat.instructions, 'メンション返信の指示' assert_includes chat.instructions, comment.sender.login_name + assert_includes chat.instructions, '人間らしい文章にする' assert_includes chat.instructions, 'external_content_toolを使って内容を確認してから返信してください。' assert_includes chat.instructions, 'GitHubのPR、ファイル、ディレクトリ、rawファイルへのURLが含まれる場合' + assert_includes chat.instructions, 'CodePenや提出物のリンク先が見えない' + assert_includes chat.instructions, 'bodyに `@mentor` を含めて' + assert_includes chat.instructions, 'メンションしてきたユーザーに「見られる状態にしてください」「内容を教えてください」と質問しないでください。' assert_includes chat.instructions, 'ピヨルドのレビューコメントに対して' assert_includes chat.instructions, 'body を空にして返信しないでください。' assert_includes chat.asked_message, comment.description diff --git a/test/agents/pjord/product_review_agent_test.rb b/test/agents/pjord/product_review_agent_test.rb index a430ab2bf44..8778d13f0e5 100644 --- a/test/agents/pjord/product_review_agent_test.rb +++ b/test/agents/pjord/product_review_agent_test.rb @@ -29,10 +29,15 @@ class Pjord::ProductReviewAgentTest < ActiveSupport::TestCase assert_includes asked_message, products(:product15).body assert_includes chat.instructions, 'あなたはFJORD BOOT CAMP' assert_includes chat.instructions, '語尾に「ピヨ」など特徴的な語尾は付けず' + assert_includes chat.instructions, 'その言語の自然で丁寧な文体で話す' + assert_includes chat.instructions, '人間らしい文章にする' assert_includes chat.instructions, '提出物にレビューコメントを書いてください。' assert_includes chat.instructions, 'reviewed_points には、提出物本文、URL先の内容、模範解答、過去コメントなどを確認して判断した具体的な点を1つ以上入れてください。' assert_includes chat.instructions, '管理側への説明、内部事情、運用者向けメモ、レビュー生成方針への言及は含めず' assert_includes chat.instructions, 'external_content_toolを使って内容を確認してからレビューしてください。' + assert_includes chat.instructions, 'CodePenや提出物のリンク先が見えない' + assert_includes chat.instructions, 'bodyに `@mentor` を含めて' + assert_includes chat.instructions, '提出者に「見られる状態にしてください」「内容を教えてください」と質問しないでください。' assert_includes chat.instructions, 'コードの特定行に対する具体的な指摘は、可能な限りgithub_pull_request_review_comment_toolを使ってPRの該当行へ直接コメントしてください。' end diff --git a/test/agents/pjord/question_answer_agent_test.rb b/test/agents/pjord/question_answer_agent_test.rb index 98b2597c2c0..79f68125256 100644 --- a/test/agents/pjord/question_answer_agent_test.rb +++ b/test/agents/pjord/question_answer_agent_test.rb @@ -18,6 +18,7 @@ class Pjord::QuestionAnswerAgentTest < ActiveSupport::TestCase assert_includes chat.instructions, 'あなたはFJORD BOOT CAMP' assert_includes chat.instructions, 'Q&A回答の指示' assert_includes chat.instructions, question.practice.title + assert_includes chat.instructions, '人間らしい文章にする' assert_includes chat.asked_message, question.title assert_includes chat.asked_message, question.description end diff --git a/test/agents/pjord/report_comment_agent_test.rb b/test/agents/pjord/report_comment_agent_test.rb index b28785f5702..902835f1fce 100644 --- a/test/agents/pjord/report_comment_agent_test.rb +++ b/test/agents/pjord/report_comment_agent_test.rb @@ -14,8 +14,12 @@ class Pjord::ReportCommentAgentTest < ActiveSupport::TestCase assert_includes chat.instructions, 'あなたはFJORD BOOT CAMP' assert_includes chat.instructions, 'ネガティブな感情' assert_includes chat.instructions, report.user.login_name + assert_includes chat.instructions, '人間らしい文章にする' assert_includes chat.instructions, 'external_content_toolを使って内容を確認してからコメントしてください。' assert_includes chat.instructions, 'GitHubのPR、ファイル、ディレクトリ、rawファイルへのURLが含まれる場合' + assert_includes chat.instructions, 'CodePenやリンク先が見えない' + assert_includes chat.instructions, 'bodyに `@mentor` を含めて' + assert_includes chat.instructions, '日報を書いたユーザーに「見られる状態にしてください」「内容を教えてください」と質問しないでください。' assert_includes chat.asked_message, report.title assert_includes chat.asked_message, report.description assert_equal [BootcampSearchTool, UserInfoTool, ExternalContentTool], chat.tools diff --git a/test/helpers/pair_works_helper_test.rb b/test/helpers/pair_works_helper_test.rb index 8579dfe36b0..2cb4e160bfa 100644 --- a/test/helpers/pair_works_helper_test.rb +++ b/test/helpers/pair_works_helper_test.rb @@ -89,4 +89,19 @@ def current_user assert_not_equal normal_meta_label, meta_label_by_status(pair_work) end end + + test 'meta_value_by_status' do + pair_work = pair_works(:pair_work2) + normal_meta_value = 'a-meta__value' + important_meta_value = 'a-meta__value is-important' + + assert_equal normal_meta_value, meta_value_by_status(pair_work) + assert_not_equal important_meta_value, meta_value_by_status(pair_work) + + reserved_on = Time.zone.local(2025, 1, 2) + travel_to reserved_on do + assert_equal important_meta_value, meta_value_by_status(pair_work) + assert_not_equal normal_meta_value, meta_value_by_status(pair_work) + end + end end diff --git a/test/integration/products/pjord_review_comment_test.rb b/test/integration/products/pjord_review_comment_test.rb index b140bfb90c2..bbe55e55f35 100644 --- a/test/integration/products/pjord_review_comment_test.rb +++ b/test/integration/products/pjord_review_comment_test.rb @@ -27,6 +27,27 @@ class Products::PjordReviewCommentTest < ActionDispatch::IntegrationTest end end + test 'does not create product review comment by Pjord when practice does not enable Pjord review' do + practice = practices(:practice6) + practice.update!(pjord_review: false) + + Pjord::ProductReviewAgent.stub(:review, ->(_product) { raise 'should not be called' }) do + assert_no_enqueued_jobs only: PjordProductReviewJob do + assert_no_difference -> { Comment.where(user: users(:pjord)).count } do + post products_path(_login_name: 'hatsuno'), + params: { practice_id: practice.id, product: { body: '提出物です。' }, commit: '提出する' } + end + end + end + + product = Product.order(:created_at).last + assert_redirected_to product_path(product) + assert_predicate product, :published_at? + assert_no_difference -> { product.comments.where(user: users(:pjord)).count } do + PjordProductReviewJob.perform_now(product_id: product.id) + end + end + test 'creates product review comment by Pjord when WIP product is submitted' do product = products(:product5) diff --git a/test/jobs/pjord_product_review_job_test.rb b/test/jobs/pjord_product_review_job_test.rb index 7ed401491b1..43981acc4be 100644 --- a/test/jobs/pjord_product_review_job_test.rb +++ b/test/jobs/pjord_product_review_job_test.rb @@ -25,6 +25,17 @@ class PjordProductReviewJobTest < ActiveJob::TestCase end end + test 'does nothing when practice does not enable Pjord review' do + product = products(:product8) + product.practice.update!(pjord_review: false) + + Pjord::ProductReviewAgent.stub(:review, ->(_product) { raise 'should not be called' }) do + assert_no_difference -> { product.comments.reload.count } do + PjordProductReviewJob.perform_now(product_id: product.id) + end + end + end + test 'does nothing when pjord user is missing' do product = products(:product8) diff --git a/test/models/comment_notifier_for_admin_test.rb b/test/models/comment_notifier_for_admin_test.rb new file mode 100644 index 00000000000..9dc239958e1 --- /dev/null +++ b/test/models/comment_notifier_for_admin_test.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'test_helper' + +class CommentNotifierForAdminTest < ActiveSupport::TestCase + test '#call notifies admins except comment sender' do + comment = comments(:commentOfTalk) + expected_count = User.admins.where.not(id: comment.sender.id).count + + assert_difference -> { AbstractNotifier::Testing::Driver.enqueued_deliveries.count }, expected_count do + CommentNotifierForAdmin.new.call(nil, nil, nil, nil, { comment: }) + end + end +end diff --git a/test/models/regular_event_test.rb b/test/models/regular_event_test.rb index 4fb8419b872..05e668b7b30 100644 --- a/test/models/regular_event_test.rb +++ b/test/models/regular_event_test.rb @@ -118,15 +118,6 @@ class RegularEventTest < ActiveSupport::TestCase assert_not regular_event.regular_event_participations.find_by(user_id: participant.id) end - test '#participated_by?' do - regular_event = regular_events(:regular_event1) - user = users(:hatsuno) - assert regular_event.participated_by?(user) - - user = users(:komagata) - assert_not regular_event.participated_by?(user) - end - test '#close_or_destroy_organizer closes the event when only one organizer exists' do user = users(:kimura) regular_event = regular_events(:regular_event5) # kimuraが1人で主催しているイベント diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 407f448daaa..1818b08c71a 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -668,6 +668,23 @@ class UserTest < ActiveSupport::TestCase assert_equal description, comment.body end + test 'comeback skips subscription in staging environment' do + user = users(:kyuukai) + + original_db_name = ENV['DB_NAME'] + ENV['DB_NAME'] = 'bootcamp_staging' + + Rails.env.stub(:production?, true) do + assert_nothing_raised do + user.comeback! + end + end + + assert_nil user.reload.hibernated_at + ensure + ENV['DB_NAME'] = original_db_name + end + test '#become_watcher!' do watchable = pages(:page1) user = users(:kimura) diff --git a/test/system/announcements_test.rb b/test/system/announcements_test.rb index c07a3cae76d..0a616cf7f98 100644 --- a/test/system/announcements_test.rb +++ b/test/system/announcements_test.rb @@ -44,7 +44,7 @@ class AnnouncementsTest < ApplicationSystemTestCase assert_selector 'p', text: 'コメント数表示のテストです。' visit current_path - assert_selector '#comment_count', text: '2' + assert_selector 'a.a-meta[href="#comments"] span.is-emphasized', text: '2' end test 'using file uploading by file selection dialogue in textarea' do diff --git a/test/system/movies_test.rb b/test/system/movies_test.rb index 862a5e8b2e2..fa899e3ebb8 100644 --- a/test/system/movies_test.rb +++ b/test/system/movies_test.rb @@ -41,14 +41,13 @@ class MoviesTest < ApplicationSystemTestCase content_type: 'video/mp4' ) visit_with_auth "/movies/#{movie.id}", 'kimura' - assert_selector '#comment_count', text: 0 wait_for_comment_form post_comment('コメント数表示のテストです。') visit_with_auth "/movies/#{movie.id}", 'kimura' wait_for_javascript_components - assert_selector '#comment_count', text: 1 + assert_selector 'a.a-meta[href="#comments"] span.is-emphasized', text: 1 end test 'show the edit movie page' do diff --git a/test/system/pages_test.rb b/test/system/pages_test.rb index 72a590ba349..7ed0d8cc27f 100644 --- a/test/system/pages_test.rb +++ b/test/system/pages_test.rb @@ -40,14 +40,13 @@ class PagesTest < ApplicationSystemTestCase test 'show comment count' do page = pages(:page1) visit_with_auth "/pages/#{page.id}", 'kimura' - assert_selector '#comment_count', text: 0 wait_for_comment_form post_comment('コメント数表示のテストです。') visit current_path wait_for_javascript_components - assert_selector '#comment_count', text: 1 + assert_selector 'span.is-emphasized', text: 1 end test 'show last updated user icon' do diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 6e8b0f7a81d..823668207d5 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -21,9 +21,11 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' + uncheck 'practice[pjord_review]', allow_label_click: true click_button '登録する' end assert_text 'プラクティスを作成しました' + assert_not_predicate Practice.order(:created_at).last, :pjord_review? end test 'create practice as a mentor' do diff --git a/test/system/product/product_list_test.rb b/test/system/product/product_list_test.rb index 5dee2c91492..8a554bbef68 100644 --- a/test/system/product/product_list_test.rb +++ b/test/system/product/product_list_test.rb @@ -37,7 +37,7 @@ class Product::ProductListTest < ApplicationSystemTestCase visit_with_auth '/products', 'komagata' product_item = find("a[href='/products/#{product.id}']").ancestor('.card-list-item') within product_item do - assert_text "コメント(#{product.comments.size})" + assert_text(/コメント(\s*#{product.comments.size}\s*)/) assert_selector '.card-list-item__user-icons' end end diff --git a/test/system/reports_test.rb b/test/system/reports_test.rb index e41c91c9ebc..8c909cbe2bc 100644 --- a/test/system/reports_test.rb +++ b/test/system/reports_test.rb @@ -19,9 +19,7 @@ class ReportsTest < ApplicationSystemTestCase test 'show number of comments' do visit_with_auth report_path(reports(:report1)), 'komagata' - within(:css, '.is-emphasized') do - assert_text '2' - end + assert_selector '.is-emphasized', text: '2' end test 'hide user icon from recent reports in report show' do diff --git a/test/system/talks/user_list_test.rb b/test/system/talks/user_list_test.rb index 4c68976038e..942f7782479 100644 --- a/test/system/talks/user_list_test.rb +++ b/test/system/talks/user_list_test.rb @@ -51,7 +51,7 @@ class UserListTest < ApplicationSystemTestCase within('.card-list-item-meta') do assert_text 'コメント' assert_selector 'img.a-user-icon' - assert_text '(1)' + assert_selector 'span.is-emphasized', text: 1 assert_text '2019年01月02日(水) 00:00' assert_text '(hajime)' end diff --git a/test/system/watches_test.rb b/test/system/watches_test.rb index e9dad73efbf..8c24a4bc782 100644 --- a/test/system/watches_test.rb +++ b/test/system/watches_test.rb @@ -87,4 +87,10 @@ class WatchesTest < ApplicationSystemTestCase assert_text 'OS X Mountain Lionをクリーンインストールする' assert_no_text '作業週1日目' end + + test 'the Watch button is displayed on a movie page' do + visit_with_auth "/movies/#{movies(:movie1).id}", 'komagata' + assert_selector '.watch-toggle' + assert_text 'Watch' + end end diff --git a/test/tools/external_content/github_reader_test.rb b/test/tools/external_content/github_reader_test.rb index ea39e09dd19..a206b70bb09 100644 --- a/test/tools/external_content/github_reader_test.rb +++ b/test/tools/external_content/github_reader_test.rb @@ -111,6 +111,13 @@ class ExternalContent::GithubReaderTest < ActiveSupport::TestCase end end + test 'asks Pjord to mention mentors when GitHub content cannot be fetched' do + stub_request(:get, 'https://raw.githubusercontent.com/fjordllc/bootcamp/main/app/models/product.rb') + .to_return(status: 404, body: 'Not Found') + + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, @reader.fetch('https://github.com/fjordllc/bootcamp/blob/main/app/models/product.rb') + end + private def stub_fetch_url(response) diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index dd9bc94e42b..886e29236a8 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -23,6 +23,15 @@ class ExternalContentToolTest < ActiveSupport::TestCase assert_not_includes result, 'ignore' end + test 'asks Pjord to mention mentors when external links cannot be fetched' do + stub_request(:get, 'https://example.com/unreadable') + .to_return(status: 404, body: 'Not Found') + + result = @tool.execute(url: 'https://example.com/unreadable') + + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result + end + test 'follows redirects' do stub_request(:get, 'https://example.com/old') .to_return(status: 302, headers: { 'Location' => '/new' }) @@ -42,7 +51,7 @@ class ExternalContentToolTest < ActiveSupport::TestCase test 'rejects private network urls' do result = @tool.execute(url: 'http://127.0.0.1/internal') - assert_equal 'URLの取得に失敗しました。', result + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result end test 'rejects redirects to private network urls' do @@ -51,6 +60,6 @@ class ExternalContentToolTest < ActiveSupport::TestCase result = @tool.execute(url: 'https://example.com/redirect-to-private') - assert_equal 'URLの取得に失敗しました。', result + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result end end