From 59a9f22dcfc08b4a7b1f8a6811717677f1982512 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 25 May 2026 21:25:25 +0900 Subject: [PATCH 01/56] =?UTF-8?q?events=5Fhelper=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E5=86=85=E5=AE=B9=E3=82=92=E8=A6=8B=E7=9B=B4=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/events_helper.rb | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6fd292802a7..802a4789fa0 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 event_comment_count(event) + 'コメント('.html_safe + content_tag(:span, event.comments.length, class: 'is-emphasized') + ')'.html_safe end def event_participant_count(event) From 7cacf2b11cdf6ae5c8520189eda335139afb2059 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 25 May 2026 21:26:09 +0900 Subject: [PATCH 02/56] =?UTF-8?q?anouncement=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/announcements/_announcement.html.slim | 7 ++++--- app/views/announcements/show.html.slim | 9 +++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/views/announcements/_announcement.html.slim b/app/views/announcements/_announcement.html.slim index 417b5595707..be714d80408 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 + = event_comment_count(announcement) diff --git a/app/views/announcements/show.html.slim b/app/views/announcements/show.html.slim index 301abc7f6fb..a705bef2806 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') + = event_comment_count(@announcement) .page-content-header__row .page-content-header-actions .page-content-header-actions__start From 8409ec9c3d1027cc64a006c59a977613cc5bfb27 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 26 May 2026 21:23:34 +0900 Subject: [PATCH 03/56] =?UTF-8?q?events=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/events/_event.html.slim | 4 +++- app/views/events/_events.html.slim | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/events/_event.html.slim b/app/views/events/_event.html.slim index bc5fa385e6e..286d6c5b59b 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') + = event_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..6af6d420726 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}) + = event_comment_count(event) = paginate @events From b33f732e4189ea036a60fd69e17a31aeb1757fe5 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 26 May 2026 21:26:00 +0900 Subject: [PATCH 04/56] =?UTF-8?q?users/events=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/events/index.html.slim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/events/index.html.slim b/app/views/users/events/index.html.slim index dd55a782c7d..0ff167b4a5e 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) + = event_comment_count(event) .pagination = paginate @events - else From b30325cdf1b99f3129dae6d899ceef7afe057ee0 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 26 May 2026 21:29:42 +0900 Subject: [PATCH 05/56] =?UTF-8?q?users/regular=5Fevents=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/regular_events/index.html.slim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/users/regular_events/index.html.slim b/app/views/users/regular_events/index.html.slim index 1d3c629e9de..f895a0fa014 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) + = event_comment_count(event) .pagination = paginate @regular_events - else From 9144ebcd6af0afdaf5a0e4fd883ba620f66a8bfe Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 26 May 2026 21:41:24 +0900 Subject: [PATCH 06/56] =?UTF-8?q?movies/:id=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/movies/_movie_header.html.slim | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/views/movies/_movie_header.html.slim b/app/views/movies/_movie_header.html.slim index b2656eef695..b722dc525ef 100644 --- a/app/views/movies/_movie_header.html.slim +++ b/app/views/movies/_movie_header.html.slim @@ -56,12 +56,9 @@ header.page-content-header .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? + a.a-meta(href='#comments') + = event_comment_count(movie) .page-content-header__row .page-content-header-actions From d5541b9eb71d08fe6888142b3f18e1124eb3932c Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 26 May 2026 21:44:24 +0900 Subject: [PATCH 07/56] =?UTF-8?q?pages=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/pages/_doc_header.html.slim | 9 +++------ app/views/pages/_page.html.slim | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/views/pages/_doc_header.html.slim b/app/views/pages/_doc_header.html.slim index 645a4a54869..2f10774052f 100644 --- a/app/views/pages/_doc_header.html.slim +++ b/app/views/pages/_doc_header.html.slim @@ -59,12 +59,9 @@ header.page-content-header .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? + a.a-meta(href='#comments') + = event_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..8e46877044a 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}) + = event_comment_count(page) From 6f224b2d95de38fb8e43768c38a2eaee3cca27a3 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 27 May 2026 08:51:19 +0900 Subject: [PATCH 08/56] =?UTF-8?q?products=E9=85=8D=E4=B8=8B=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/products/product_component.html.slim | 4 ++-- app/views/products/_product.html.slim | 2 +- app/views/products/_product_header.html.slim | 9 +++------ app/views/products/_product_list_item.html.slim | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/app/components/products/product_component.html.slim b/app/components/products/product_component.html.slim index 8e5e32a60da..730239a58e9 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.event_comment_count(@product) .card-list-item-meta__item .card-list-item__user-icons - @product.commented_users.distinct.each do |user| diff --git a/app/views/products/_product.html.slim b/app/views/products/_product.html.slim index 78da650c7ce..b8c35f9f960 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}) + = event_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..ef9864db658 100644 --- a/app/views/products/_product_header.html.slim +++ b/app/views/products/_product_header.html.slim @@ -71,12 +71,9 @@ header.page-content-header.is-product .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? + a.a-meta(href='#comments') + = event_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..3f313e8cf0d 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 = event_comment_count(product) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'users/icon', From 1c8e6a7bd7d5bba9d1470415792c7797837da0d3 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 27 May 2026 09:12:02 +0900 Subject: [PATCH 09/56] =?UTF-8?q?regular=5Fevents=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/regular_events/_regular_event.html.slim | 9 +++------ app/views/regular_events/_regular_events.html.slim | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/views/regular_events/_regular_event.html.slim b/app/views/regular_events/_regular_event.html.slim index cd7166e6152..d781bcd56d8 100644 --- a/app/views/regular_events/_regular_event.html.slim +++ b/app/views/regular_events/_regular_event.html.slim @@ -24,12 +24,9 @@ = 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? + a.a-meta(href='#comments') + = event_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..cc2086b5ce0 100644 --- a/app/views/regular_events/_regular_events.html.slim +++ b/app/views/regular_events/_regular_events.html.slim @@ -48,8 +48,8 @@ ul.card-list.a-card 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}) + = event_comment_count(event) = paginate @regular_events From 708f9c774ec660d55da415f042356211de92ff45 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 27 May 2026 10:16:05 +0900 Subject: [PATCH 10/56] =?UTF-8?q?reports=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/reports/_product.html.slim | 2 +- app/views/reports/_report.html.slim | 2 +- app/views/reports/_report_header.html.slim | 9 +++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/views/reports/_product.html.slim b/app/views/reports/_product.html.slim index 6b8752b96af..6208ace5354 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}) + = event_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..bc1a593d5a7 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}) + = event_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..f3281589da5 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') + = event_comment_count(report) .page-content-header__row .page-content-header-actions From 75ff6eb6c921f85edc7d4518cadd4ecef0242610 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 27 May 2026 10:43:45 +0900 Subject: [PATCH 11/56] =?UTF-8?q?pair=5Fwork=E3=81=AE=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=81=A8CSS=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/assets/stylesheets/atoms/_a-meta.css | 4 ++++ app/views/pair_works/_pair_work.html.slim | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/atoms/_a-meta.css b/app/assets/stylesheets/atoms/_a-meta.css index 6cc0054b364..9ecbb46d306 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/views/pair_works/_pair_work.html.slim b/app/views/pair_works/_pair_work.html.slim index d019380dc03..b06e0c847da 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") + = event_comment_count(pair_work) - if pair_work.solved? .stamp.is-circle.is-solved From 74186fdcf8d581955433d88193fc0909d903060f Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 27 May 2026 10:48:55 +0900 Subject: [PATCH 12/56] =?UTF-8?q?talk=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E5=AE=9F=E8=A3=85=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/talks/_talk.html.slim | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/app/views/talks/_talk.html.slim b/app/views/talks/_talk.html.slim index d7a6ab4bc9d..cbeec6575c4 100644 --- a/app/views/talks/_talk.html.slim +++ b/app/views/talks/_talk.html.slim @@ -8,31 +8,30 @@ 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 - .card-list-item-meta - .card-list-item-meta__items - .card-list-item-meta__item - .a-meta - | コメント(#{talk.comments.size}) - .card-list-item-meta__item - .card-list-item__user-icons - = render partial: 'users/icon', - collection: talk.commented_users.distinct, - locals: { link_class: 'card-list-item__user-icons-icon', image_class: 'a-user-icon' }, - as: :user, - cached: true - .card-list-item-meta__item - .a-meta - | 〜 #{l talk.comments.last.updated_at} - .card-list-item-meta__item - - if talk.comments.last.user.admin + .card-list-item__row + .card-list-item-meta__items + .card-list-item-meta__item + .card-list-item-meta + .card-list-item-meta__items + .card-list-item-meta__item .a-meta - | (管理者) - - else + = event_comment_count(talk) + .card-list-item-meta__item + .card-list-item__user-icons + = render partial: 'users/icon', + collection: talk.commented_users.distinct, + locals: { link_class: 'card-list-item__user-icons-icon', image_class: 'a-user-icon' }, + as: :user, + cached: true + .card-list-item-meta__item .a-meta - | (#{talk.user.login_name}) + | 〜 #{l talk.comments.last.updated_at} + .card-list-item-meta__item + - if talk.comments.last.user.admin + .a-meta + | (管理者) + - else + .a-meta + | (#{talk.user.login_name}) From e0635feb52e2cd86e6e10418ace5640f0b433486 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Thu, 28 May 2026 21:35:02 +0900 Subject: [PATCH 13/56] =?UTF-8?q?companies/:id/products=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E5=AE=9F=E8=A3=85=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/companies/products/_product.html.slim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/companies/products/_product.html.slim b/app/views/companies/products/_product.html.slim index 0976b956832..adf12b4bc17 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}) + = event_comment_count(product) .card-list-item-meta__item .card-list-item__user-icons - commented_users[product.id].uniq.each do |user| From 945a47c9feec5e0b8f63bbf89a080801fe916f47 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 1 Jun 2026 20:16:45 +0900 Subject: [PATCH 14/56] =?UTF-8?q?=E3=82=A2=E3=83=89=E3=83=90=E3=82=A4?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=A7=E6=89=80=E5=B1=9E=E4=BC=81=E6=A5=AD=E3=81=AB=E6=89=80?= =?UTF-8?q?=E5=B1=9E=E3=81=97=E3=83=88=E3=83=83=E3=83=97=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=A7=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E7=A0=94=E4=BF=AE=E7=94=9F=E3=81=AE=E6=97=A5=E5=A0=B1=E4=B8=80?= =?UTF-8?q?=E8=A6=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/home/_colleague_trainee.html.slim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/_colleague_trainee.html.slim b/app/views/home/_colleague_trainee.html.slim index e86b92666e0..904d8784d6c 100644 --- a/app/views/home/_colleague_trainee.html.slim +++ b/app/views/home/_colleague_trainee.html.slim @@ -22,4 +22,4 @@ - if user.comments.present? .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}) + = event_comment_count(user) From 7c1d77611ea7bf0c12bb0e17b3f3c0243f639dfb Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 1 Jun 2026 20:38:01 +0900 Subject: [PATCH 15/56] =?UTF-8?q?html=5Fsafe=E3=81=8B=E3=82=89safe=5Fjoin?= =?UTF-8?q?=E3=81=AB=E5=87=A6=E7=90=86=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/events_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 802a4789fa0..2886b4f7278 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -14,7 +14,7 @@ def google_calendar_url(event) end def event_comment_count(event) - 'コメント('.html_safe + content_tag(:span, event.comments.length, class: 'is-emphasized') + ')'.html_safe + safe_join(['コメント(', content_tag(:span, event.comments.length, class: 'is-emphasized'), ')']) end def event_participant_count(event) From d2a40fd9a0a76735f805b554595bea3d40ecbf5c Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Tue, 2 Jun 2026 20:30:22 +0900 Subject: [PATCH 16/56] =?UTF-8?q?rails=20test=E3=81=A7=E5=A4=B1=E6=95=97?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=83=86=E3=82=B9=E3=83=88=E6=96=87=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/system/announcements_test.rb | 2 +- test/system/movies_test.rb | 3 +-- test/system/pages_test.rb | 3 +-- test/system/product/product_list_test.rb | 2 +- test/system/reports_test.rb | 4 +--- test/system/talks/user_list_test.rb | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/test/system/announcements_test.rb b/test/system/announcements_test.rb index c07a3cae76d..38a0b6c5c81 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 'span', 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..1a4a0a1c707 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 'span', 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..9de4a80b270 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', text: 1 end test 'show last updated user icon' 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..b50753c1b4b 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_text '1' assert_text '2019年01月02日(水) 00:00' assert_text '(hajime)' end From cba3c715cc2e08ecf9afdd86fc2168e2a5a0e703 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Wed, 3 Jun 2026 15:21:05 +0900 Subject: [PATCH 17/56] =?UTF-8?q?coderabbit=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/events_helper.rb | 2 +- app/views/movies/_movie_header.html.slim | 13 +++--- app/views/pages/_doc_header.html.slim | 9 ++-- app/views/products/_product_header.html.slim | 7 ++- .../regular_events/_regular_event.html.slim | 6 +-- .../regular_events/_regular_events.html.slim | 1 - app/views/talks/_talk.html.slim | 46 +++++++++---------- test/system/announcements_test.rb | 2 +- test/system/movies_test.rb | 2 +- test/system/pages_test.rb | 2 +- test/system/talks/user_list_test.rb | 2 +- 11 files changed, 44 insertions(+), 48 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 2886b4f7278..2c0473a42e3 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -14,7 +14,7 @@ def google_calendar_url(event) end def event_comment_count(event) - safe_join(['コメント(', content_tag(:span, event.comments.length, class: 'is-emphasized'), ')']) + safe_join(['コメント(', content_tag(:span, event.comments.size, class: 'is-emphasized'), ')']) end def event_participant_count(event) diff --git a/app/views/movies/_movie_header.html.slim b/app/views/movies/_movie_header.html.slim index b722dc525ef..76062b4f48f 100644 --- a/app/views/movies/_movie_header.html.slim +++ b/app/views/movies/_movie_header.html.slim @@ -52,13 +52,12 @@ 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 - - if movie.comments.any? - a.a-meta(href='#comments') - = event_comment_count(movie) + - if movie.comments.any? + .page-content-header-metas__end + .page-content-header-metas + .page-content-header-metas__meta + a.a-meta(href='#comments') + = event_comment_count(movie) .page-content-header__row .page-content-header-actions diff --git a/app/views/pages/_doc_header.html.slim b/app/views/pages/_doc_header.html.slim index 2f10774052f..eaf5ca97052 100644 --- a/app/views/pages/_doc_header.html.slim +++ b/app/views/pages/_doc_header.html.slim @@ -55,11 +55,10 @@ 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 - - if page.comments.any? + - if page.comments.any? + .page-content-header-metas__end + .page-content-header-metas + .page-content-header-metas__meta a.a-meta(href='#comments') = event_comment_count(page) diff --git a/app/views/products/_product_header.html.slim b/app/views/products/_product_header.html.slim index ef9864db658..da21b7b9e57 100644 --- a/app/views/products/_product_header.html.slim +++ b/app/views/products/_product_header.html.slim @@ -68,10 +68,9 @@ 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 - - if product.comments.any? + - if product.comments.any? + .page-content-header-metas__end + .page-content-header-metas__meta a.a-meta(href='#comments') = event_comment_count(product) diff --git a/app/views/regular_events/_regular_event.html.slim b/app/views/regular_events/_regular_event.html.slim index d781bcd56d8..4fc24b122d9 100644 --- a/app/views/regular_events/_regular_event.html.slim +++ b/app/views/regular_events/_regular_event.html.slim @@ -22,9 +22,9 @@ .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 - - if regular_event.comments.any? + - if regular_event.comments.any? + .page-content-header-metas__end + .page-content-header-metas__meta a.a-meta(href='#comments') = event_comment_count(regular_event) diff --git a/app/views/regular_events/_regular_events.html.slim b/app/views/regular_events/_regular_events.html.slim index cc2086b5ce0..a662205b5dc 100644 --- a/app/views/regular_events/_regular_events.html.slim +++ b/app/views/regular_events/_regular_events.html.slim @@ -47,7 +47,6 @@ 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.any? .card-list-item-meta__item .a-meta diff --git a/app/views/talks/_talk.html.slim b/app/views/talks/_talk.html.slim index cbeec6575c4..0d88c74a010 100644 --- a/app/views/talks/_talk.html.slim +++ b/app/views/talks/_talk.html.slim @@ -10,28 +10,28 @@ | #{talk.user.long_name} さんの相談部屋 - if talk.comments.any? hr.card-list-item__row-separator - .card-list-item__row - .card-list-item-meta__items - .card-list-item-meta__item - .card-list-item-meta - .card-list-item-meta__items - .card-list-item-meta__item + .card-list-item__row + .card-list-item-meta__items + .card-list-item-meta__item + .card-list-item-meta + .card-list-item-meta__items + .card-list-item-meta__item + .a-meta + = event_comment_count(talk) + .card-list-item-meta__item + .card-list-item__user-icons + = render partial: 'users/icon', + collection: talk.commented_users.distinct, + locals: { link_class: 'card-list-item__user-icons-icon', image_class: 'a-user-icon' }, + as: :user, + cached: true + .card-list-item-meta__item + .a-meta + | 〜 #{l talk.comments.last.updated_at} + .card-list-item-meta__item + - if talk.comments.last.user.admin .a-meta - = event_comment_count(talk) - .card-list-item-meta__item - .card-list-item__user-icons - = render partial: 'users/icon', - collection: talk.commented_users.distinct, - locals: { link_class: 'card-list-item__user-icons-icon', image_class: 'a-user-icon' }, - as: :user, - cached: true - .card-list-item-meta__item + | (管理者) + - else .a-meta - | 〜 #{l talk.comments.last.updated_at} - .card-list-item-meta__item - - if talk.comments.last.user.admin - .a-meta - | (管理者) - - else - .a-meta - | (#{talk.user.login_name}) + | (#{talk.user.login_name}) diff --git a/test/system/announcements_test.rb b/test/system/announcements_test.rb index 38a0b6c5c81..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 'span', 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 1a4a0a1c707..fa899e3ebb8 100644 --- a/test/system/movies_test.rb +++ b/test/system/movies_test.rb @@ -47,7 +47,7 @@ class MoviesTest < ApplicationSystemTestCase visit_with_auth "/movies/#{movie.id}", 'kimura' wait_for_javascript_components - assert_selector 'span', 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 9de4a80b270..7ed0d8cc27f 100644 --- a/test/system/pages_test.rb +++ b/test/system/pages_test.rb @@ -46,7 +46,7 @@ class PagesTest < ApplicationSystemTestCase visit current_path wait_for_javascript_components - assert_selector 'span', text: 1 + assert_selector 'span.is-emphasized', text: 1 end test 'show last updated user icon' do diff --git a/test/system/talks/user_list_test.rb b/test/system/talks/user_list_test.rb index b50753c1b4b..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 From cd501215fb490283d0bd180b9e1a38f786459002 Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 15 Jun 2026 09:27:12 +0900 Subject: [PATCH 18/56] =?UTF-8?q?comment=E3=83=98=E3=83=AB=E3=83=91?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E5=90=8D?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/events_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 2c0473a42e3..672fd2ac18d 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -13,8 +13,8 @@ def google_calendar_url(event) uri.to_s end - def event_comment_count(event) - safe_join(['コメント(', content_tag(:span, event.comments.size, class: 'is-emphasized'), ')']) + def comment_count(commentable) + safe_join(['コメント(', content_tag(:span, commentable.comments.size, class: 'is-emphasized'), ')']) end def event_participant_count(event) From 72fc6a610c09b27578650ffd590ef9cf6b51530c Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 15 Jun 2026 10:06:01 +0900 Subject: [PATCH 19/56] =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E5=90=8D=E5=A4=89=E6=9B=B4=E3=81=AB=E3=82=88=E3=82=8Bviews?= =?UTF-8?q?=E3=82=82=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/products/product_component.html.slim | 2 +- app/views/announcements/_announcement.html.slim | 2 +- app/views/announcements/show.html.slim | 2 +- app/views/companies/products/_product.html.slim | 2 +- app/views/events/_event.html.slim | 2 +- app/views/events/_events.html.slim | 2 +- app/views/home/_colleague_trainee.html.slim | 4 ++-- app/views/movies/_movie_header.html.slim | 2 +- app/views/pages/_doc_header.html.slim | 2 +- app/views/pages/_page.html.slim | 2 +- app/views/pair_works/_pair_work.html.slim | 2 +- app/views/products/_product.html.slim | 2 +- app/views/products/_product_header.html.slim | 2 +- app/views/products/_product_list_item.html.slim | 2 +- app/views/regular_events/_regular_event.html.slim | 2 +- app/views/regular_events/_regular_events.html.slim | 2 +- app/views/reports/_product.html.slim | 2 +- app/views/reports/_report.html.slim | 2 +- app/views/reports/_report_header.html.slim | 2 +- app/views/talks/_talk.html.slim | 2 +- app/views/users/events/index.html.slim | 2 +- app/views/users/regular_events/index.html.slim | 2 +- 22 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/components/products/product_component.html.slim b/app/components/products/product_component.html.slim index 730239a58e9..76c773e6004 100644 --- a/app/components/products/product_component.html.slim +++ b/app/components/products/product_component.html.slim @@ -69,7 +69,7 @@ .card-list-item-meta .card-list-item-meta__items .card-list-item-meta__item - .a-meta = helpers.event_comment_count(@product) + .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/views/announcements/_announcement.html.slim b/app/views/announcements/_announcement.html.slim index be714d80408..c818c47d57d 100644 --- a/app/views/announcements/_announcement.html.slim +++ b/app/views/announcements/_announcement.html.slim @@ -33,4 +33,4 @@ - if announcement.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(announcement) + = comment_count(announcement) diff --git a/app/views/announcements/show.html.slim b/app/views/announcements/show.html.slim index a705bef2806..e30306e2900 100644 --- a/app/views/announcements/show.html.slim +++ b/app/views/announcements/show.html.slim @@ -47,7 +47,7 @@ hr.a-border .page-content-header-metas__meta - if @announcement.comments.any? a.a-meta(href='#comments') - = event_comment_count(@announcement) + = 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 adf12b4bc17..2b1ecc99f5b 100644 --- a/app/views/companies/products/_product.html.slim +++ b/app/views/companies/products/_product.html.slim @@ -44,7 +44,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - = event_comment_count(product) + = 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 286d6c5b59b..6512235a9d2 100644 --- a/app/views/events/_event.html.slim +++ b/app/views/events/_event.html.slim @@ -28,7 +28,7 @@ .page-content-header-metas__meta - if event.comments.any? a.a-meta(href='#comments') - = event_comment_count(event) + = 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 6af6d420726..90e1bd77aff 100644 --- a/app/views/events/_events.html.slim +++ b/app/views/events/_events.html.slim @@ -39,5 +39,5 @@ ul.card-list.a-card - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event) + = comment_count(event) = paginate @events diff --git a/app/views/home/_colleague_trainee.html.slim b/app/views/home/_colleague_trainee.html.slim index 904d8784d6c..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 - = event_comment_count(user) + = comment_count(user) diff --git a/app/views/movies/_movie_header.html.slim b/app/views/movies/_movie_header.html.slim index 76062b4f48f..624e2b251b4 100644 --- a/app/views/movies/_movie_header.html.slim +++ b/app/views/movies/_movie_header.html.slim @@ -57,7 +57,7 @@ header.page-content-header .page-content-header-metas .page-content-header-metas__meta a.a-meta(href='#comments') - = event_comment_count(movie) + = comment_count(movie) .page-content-header__row .page-content-header-actions diff --git a/app/views/pages/_doc_header.html.slim b/app/views/pages/_doc_header.html.slim index eaf5ca97052..5dc8796c405 100644 --- a/app/views/pages/_doc_header.html.slim +++ b/app/views/pages/_doc_header.html.slim @@ -60,7 +60,7 @@ header.page-content-header .page-content-header-metas .page-content-header-metas__meta a.a-meta(href='#comments') - = event_comment_count(page) + = 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 8e46877044a..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 - = event_comment_count(page) + = comment_count(page) diff --git a/app/views/pair_works/_pair_work.html.slim b/app/views/pair_works/_pair_work.html.slim index b06e0c847da..e8934468ec1 100644 --- a/app/views/pair_works/_pair_work.html.slim +++ b/app/views/pair_works/_pair_work.html.slim @@ -43,7 +43,7 @@ - if pair_work.comments.any? .card-list-item-meta__item .a-meta(class="is-important") - = event_comment_count(pair_work) + = 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 b8c35f9f960..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 - = event_comment_count(product) + = 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 da21b7b9e57..6f9fcc9a53f 100644 --- a/app/views/products/_product_header.html.slim +++ b/app/views/products/_product_header.html.slim @@ -72,7 +72,7 @@ header.page-content-header.is-product .page-content-header-metas__end .page-content-header-metas__meta a.a-meta(href='#comments') - = event_comment_count(product) + = 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 3f313e8cf0d..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 = event_comment_count(product) + .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 4fc24b122d9..ecc4715c707 100644 --- a/app/views/regular_events/_regular_event.html.slim +++ b/app/views/regular_events/_regular_event.html.slim @@ -26,7 +26,7 @@ .page-content-header-metas__end .page-content-header-metas__meta a.a-meta(href='#comments') - = event_comment_count(regular_event) + = 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 a662205b5dc..5475b2e830c 100644 --- a/app/views/regular_events/_regular_events.html.slim +++ b/app/views/regular_events/_regular_events.html.slim @@ -50,5 +50,5 @@ ul.card-list.a-card - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event) + = comment_count(event) = paginate @regular_events diff --git a/app/views/reports/_product.html.slim b/app/views/reports/_product.html.slim index 6208ace5354..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 - = event_comment_count(product) + = 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 bc1a593d5a7..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 - = event_comment_count(report) + = 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 f3281589da5..f29ea3acedc 100644 --- a/app/views/reports/_report_header.html.slim +++ b/app/views/reports/_report_header.html.slim @@ -42,7 +42,7 @@ header.page-content-header.is-report .page-content-header-metas__meta - if report.comments.any? a.a-meta(href='#comments') - = event_comment_count(report) + = 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 0d88c74a010..840d63ec57a 100644 --- a/app/views/talks/_talk.html.slim +++ b/app/views/talks/_talk.html.slim @@ -17,7 +17,7 @@ .card-list-item-meta__items .card-list-item-meta__item .a-meta - = event_comment_count(talk) + = comment_count(talk) .card-list-item-meta__item .card-list-item__user-icons = render partial: 'users/icon', diff --git a/app/views/users/events/index.html.slim b/app/views/users/events/index.html.slim index 0ff167b4a5e..1d5c2875de3 100644 --- a/app/views/users/events/index.html.slim +++ b/app/views/users/events/index.html.slim @@ -60,7 +60,7 @@ - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event) + = 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 f895a0fa014..17ae63e8c77 100644 --- a/app/views/users/regular_events/index.html.slim +++ b/app/views/users/regular_events/index.html.slim @@ -59,7 +59,7 @@ - if event.comments.any? .card-list-item-meta__item .a-meta - = event_comment_count(event) + = comment_count(event) .pagination = paginate @regular_events - else From def9e248be508b5102bbb1a7966b9819b957d6ca Mon Sep 17 00:00:00 2001 From: zecky1120 Date: Mon, 15 Jun 2026 15:34:58 +0900 Subject: [PATCH 20/56] =?UTF-8?q?N+1=E3=82=AF=E3=82=A8=E3=83=AA=E5=AF=BE?= =?UTF-8?q?=E7=AD=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/reports_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 63413607521..4ed236ae670 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -14,7 +14,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 From 65ea891328acdf3cb9c355d358a44b3eeced3a5b Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Fri, 15 May 2026 11:21:58 +0900 Subject: [PATCH 21/56] =?UTF-8?q?=E4=BC=91=E4=BC=9A=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92Hibernation=E3=83=A2=E3=83=87=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E3=81=BE=E3=81=A8=E3=82=81=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/hibernation_controller.rb | 28 ++------------------ app/models/hibernation.rb | 31 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/app/controllers/hibernation_controller.rb b/app/controllers/hibernation_controller.rb index cfc10955b8a..b9af4e40f88 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.excute + 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/models/hibernation.rb b/app/models/hibernation.rb index a8ab345ca14..4a4e3ba9b14 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -4,4 +4,35 @@ class Hibernation < ApplicationRecord belongs_to :user validates :reason, presence: true validates :scheduled_return_on, presence: true + + def excute + update_hibernated_at! + destroy_subscription! + notify_to_chat + notify_to_mentors_and_admins + user.clean_up_regular_events + 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 From ec1099693fad8a3b99962cbf1d8450675af6667c Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Fri, 15 May 2026 17:17:37 +0900 Subject: [PATCH 22/56] =?UTF-8?q?=E7=AE=A1=E7=90=86=E8=80=85=E3=81=8C?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E6=83=85=E5=A0=B1=E5=A4=89=E6=9B=B4=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=8B=E3=82=89=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E4=BC=91=E4=BC=9A=E3=83=BB=E5=BE=A9=E5=B8=B0=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/users_controller.rb | 19 ++++++++++++++ app/controllers/hibernation_controller.rb | 2 +- app/models/hibernation.rb | 2 +- app/models/user.rb | 6 +++-- app/views/users/_form.html.slim | 30 +++++++++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 6126d4729f6..b6b3c627f62 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -35,6 +35,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] + hibernate(@user) + elsif params[:comeback_user] + @user.comeback! + end redirect_to user_url(@user), notice: 'ユーザー情報を更新しました。' else render :edit @@ -92,6 +97,7 @@ def user_params :organization, :os, :study_place, { experiences: [] }, :company_id, :trainee, :nda, :avatar, + :hibernated_at, :graduated_on, :retired_on, :job_seeker, :github_collaborator, :officekey_permission, :tag_list, :training_ends_on, :training_completed_at, @@ -110,4 +116,17 @@ def complete_graduation_or_retirement(user) Subscription.new.destroy(user.subscription_id) if user.subscription_id? end end + + def hibernate(user) + hibernation = Hibernation.new( + reason: '管理者操作', + scheduled_return_on: params[:scheduled_return_on] + ) + hibernation.user = @user + if hibernation.save + hibernation.execute + else + render :edit, alert: '休会に失敗しました。' + end + end end diff --git a/app/controllers/hibernation_controller.rb b/app/controllers/hibernation_controller.rb index b9af4e40f88..26b5101c2a7 100644 --- a/app/controllers/hibernation_controller.rb +++ b/app/controllers/hibernation_controller.rb @@ -15,7 +15,7 @@ def create @hibernation.user = current_user if @hibernation.save - @hibernation.excute + @hibernation.execute logout redirect_to hibernation_path diff --git a/app/models/hibernation.rb b/app/models/hibernation.rb index 4a4e3ba9b14..0cbe26482e1 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -5,7 +5,7 @@ class Hibernation < ApplicationRecord validates :reason, presence: true validates :scheduled_return_on, presence: true - def excute + def execute update_hibernated_at! destroy_subscription! notify_to_chat diff --git a/app/models/user.rb b/app/models/user.rb index 84bee46c9d5..96d70b5da96 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -847,10 +847,12 @@ def update_last_returned_at! def comeback! update_last_returned_at! - subscription = Subscription.new.create(customer_id, trial: 0) + unless Rails.env.development? + 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/views/users/_form.html.slim b/app/views/users/_form.html.slim index 0d05c870533..ee86f3977ee 100644 --- a/app/views/users/_form.html.slim +++ b/app/views/users/_form.html.slim @@ -97,6 +97,36 @@ | 以下管理者のみ操作ができます = render 'users/form/course', f: f + .form-item-block + .form-item-block__inner + header.form-item-block__header + .form-item-block__title + | 休会管理 + + - if @user.hibernated? + .block-checks.is-1-item + .block-checks__item + .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 + .block-checks.is-1-item + .block-checks__item + .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 + = date_field_tag :scheduled_return_on, nil, class: 'a-text-input js-date-input-toggler-date' + .a-form-help + p + - if @user.hibernated? + | 現在このユーザーは、休会中です。 + - else + | 現在このユーザーは、利用中です。 + .form-item-block .form-item-block__inner header.form-item-block__header From 044b7edb291a4cef27e368cf6cc16fea90ef5ee4 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 18 May 2026 14:48:14 +0900 Subject: [PATCH 23/56] =?UTF-8?q?CodeRabbit=E6=8C=87=E6=91=98=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C:=20Stripe=E5=87=A6=E7=90=86=E3=81=AE=E7=92=B0?= =?UTF-8?q?=E5=A2=83=E6=9D=A1=E4=BB=B6=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 96d70b5da96..353b06f6320 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -847,7 +847,7 @@ def update_last_returned_at! def comeback! update_last_returned_at! - unless Rails.env.development? + if Rails.env.production? && !staging? subscription = Subscription.new.create(customer_id, trial: 0) self.subscription_id = subscription['id'] end From 02222e8cdfc467fcc5099fc557a5fa7710b34b1c Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 18 May 2026 14:56:01 +0900 Subject: [PATCH 24/56] =?UTF-8?q?CodeRabbit=E6=8C=87=E6=91=98=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C:=20AbstractController::DoubleRenderError=E3=81=AE?= =?UTF-8?q?=E8=A7=A3=E6=B6=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/users_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index b6b3c627f62..a164f693cf7 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -36,7 +36,7 @@ def update if @user.update(user_params) complete_graduation_or_retirement(@user) if params[:hibernate_user] - hibernate(@user) + return unless hibernate(`@user`) elsif params[:comeback_user] @user.comeback! end @@ -122,11 +122,12 @@ def hibernate(user) reason: '管理者操作', scheduled_return_on: params[:scheduled_return_on] ) - hibernation.user = @user + hibernation.user = user if hibernation.save hibernation.execute else - render :edit, alert: '休会に失敗しました。' + render :edit + false end end end From 0fc474e9212a5d11b0f4cf0da489e257213061f0 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 18 May 2026 14:56:58 +0900 Subject: [PATCH 25/56] =?UTF-8?q?CodeRabbit=E6=8C=87=E6=91=98=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C:=20user.subscription=5Fid=20=E3=81=8C=E7=A9=BA?= =?UTF-8?q?=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=AF=E9=80=9A=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/hibernation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/hibernation.rb b/app/models/hibernation.rb index 0cbe26482e1..068a89633e4 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -23,7 +23,7 @@ def update_hibernated_at! def destroy_subscription! return nil if !Rails.env.production? || staging? - Subscription.new.destroy(user.subscription_id) if user.subscription_id + Subscription.new.destroy(user.subscription_id) if user.subscription_id? end def notify_to_mentors_and_admins From 3b660acbc052941465ca8731936854f24050353b Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 18 May 2026 15:35:51 +0900 Subject: [PATCH 26/56] =?UTF-8?q?=E6=97=A5=E4=BB=98=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E6=AC=84=E3=81=AE=E8=AA=AC=E6=98=8E=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/users_controller.rb | 2 +- app/views/users/_form.html.slim | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index a164f693cf7..0fdd86e564f 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -36,7 +36,7 @@ def update if @user.update(user_params) complete_graduation_or_retirement(@user) if params[:hibernate_user] - return unless hibernate(`@user`) + return unless hibernate(@user) elsif params[:comeback_user] @user.comeback! end diff --git a/app/views/users/_form.html.slim b/app/views/users/_form.html.slim index ee86f3977ee..a6c6dd0b1d0 100644 --- a/app/views/users/_form.html.slim +++ b/app/views/users/_form.html.slim @@ -118,8 +118,11 @@ = 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 - = date_field_tag :scheduled_return_on, nil, class: 'a-text-input js-date-input-toggler-date' + .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? From e01bb52caf90074aa5f5fefea061cef0fac640ab Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 18 May 2026 16:57:58 +0900 Subject: [PATCH 27/56] =?UTF-8?q?Rubocop=E3=81=AE=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6=E3=80=81=E3=82=B3?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 0fdd86e564f..1345b208a9e 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Admin::UsersController < AdminController +class Admin::UsersController < AdminController # rubocop:todo Metrics/ClassLength before_action :set_user, only: %i[show edit update] ALLOWED_TARGETS = %w[all student_and_trainee inactive hibernated retired graduate adviser mentor trainee year_end_party campaign].freeze From 87f5778a0c7219830fb12642d0c16749c3a3d8e0 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Tue, 19 May 2026 10:20:49 +0900 Subject: [PATCH 28/56] =?UTF-8?q?/app/models/hibernation.rb=20=E3=81=ABsta?= =?UTF-8?q?ging=3F=E3=82=92=E5=AE=9A=E7=BE=A9=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/hibernation.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/hibernation.rb b/app/models/hibernation.rb index 068a89633e4..a6d442a576c 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -20,6 +20,10 @@ def update_hibernated_at! user.save!(validate: false) end + def staging? + ENV['DB_NAME'] == 'bootcamp_staging' + end + def destroy_subscription! return nil if !Rails.env.production? || staging? From f93f728e00fd9299dae80201a2b3546319523268 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 1 Jun 2026 10:04:46 +0900 Subject: [PATCH 29/56] =?UTF-8?q?Slim=E3=81=AE=E6=A7=8B=E9=80=A0=E3=81=8C?= =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=9F?= =?UTF-8?q?=E3=82=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/users/_form.html.slim | 36 ++++++++++++++++----------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/views/users/_form.html.slim b/app/views/users/_form.html.slim index a6c6dd0b1d0..05ec4795128 100644 --- a/app/views/users/_form.html.slim +++ b/app/views/users/_form.html.slim @@ -103,26 +103,26 @@ .form-item-block__title | 休会管理 - - if @user.hibernated? - .block-checks.is-1-item - .block-checks__item - .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' + .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 - .block-checks.is-1-item - .block-checks__item - .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' + - 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' + .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? From 1b11e7cb7e7c20f3f59c5676cfd3686e4d5ff615 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Wed, 17 Jun 2026 11:30:34 +0900 Subject: [PATCH 30/56] =?UTF-8?q?=E7=AE=A1=E7=90=86=E8=80=85=E3=81=AB?= =?UTF-8?q?=E3=82=88=E3=82=8B=E4=BC=91=E4=BC=9A=E5=87=A6=E7=90=86=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92Admin::UserController?= =?UTF-8?q?=E3=81=8B=E3=82=89hibernation=E3=83=A2=E3=83=87=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/admin/users_controller.rb | 25 +++++------------------ app/models/hibernation.rb | 14 +++++++++++++ 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1345b208a9e..9b954e205bd 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Admin::UsersController < AdminController # rubocop:todo Metrics/ClassLength +class Admin::UsersController < AdminController before_action :set_user, only: %i[show edit update] ALLOWED_TARGETS = %w[all student_and_trainee inactive hibernated retired graduate adviser mentor trainee year_end_party campaign].freeze @@ -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) @@ -36,7 +37,7 @@ def update if @user.update(user_params) complete_graduation_or_retirement(@user) if params[:hibernate_user] - return unless hibernate(@user) + return unless Hibernation.hibernate_by_admin(user: @user, scheduled_return_on: params[:scheduled_return_on]) elsif params[:comeback_user] @user.comeback! end @@ -96,10 +97,8 @@ def user_params :password, :password_confirmation, :job, :organization, :os, :study_place, { experiences: [] }, :company_id, - :trainee, :nda, :avatar, - :hibernated_at, - :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, @@ -116,18 +115,4 @@ def complete_graduation_or_retirement(user) Subscription.new.destroy(user.subscription_id) if user.subscription_id? end end - - def hibernate(user) - hibernation = Hibernation.new( - reason: '管理者操作', - scheduled_return_on: params[:scheduled_return_on] - ) - hibernation.user = user - if hibernation.save - hibernation.execute - else - render :edit - false - end - end end diff --git a/app/models/hibernation.rb b/app/models/hibernation.rb index a6d442a576c..80e0274830f 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -13,6 +13,20 @@ def execute 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! From d35564350b8482a8134529b397ad20741a6b467e Mon Sep 17 00:00:00 2001 From: zamami Date: Wed, 17 Jun 2026 20:35:52 +0900 Subject: [PATCH 31/56] =?UTF-8?q?CommentNotifierForAdmin=E3=82=92ActiveSup?= =?UTF-8?q?port::Notifications=E3=81=AB=E7=A7=BB=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 相談部屋コメント時の管理者通知イベントをNewspaper.publishからActiveSupport::Notifications.instrumentへ置き換えた。 CommentNotifierForAdmin#callをActiveSupport::Notificationsのsubscriberとして呼ばれる5引数の形式に変更した。 came_comment_in_talkイベントをactive_support_notifications.rbで購読し、CommentNotifierForAdminが通知処理を受け取れるようにした。 --- app/models/comment/after_create_callback.rb | 2 +- app/models/comment_notifier_for_admin.rb | 2 +- config/initializers/active_support_notifications.rb | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) 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/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) From 4d0403dd74a57fd8acab8d1b629463bce89bbd05 Mon Sep 17 00:00:00 2001 From: zamami Date: Wed, 17 Jun 2026 20:42:39 +0900 Subject: [PATCH 32/56] =?UTF-8?q?=E7=AE=A1=E7=90=86=E8=80=85=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=B6=E3=83=BC=E5=85=A8=E5=93=A1=E3=81=AE=E3=81=86?= =?UTF-8?q?=E3=81=A1=E3=80=81=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E6=9B=B8=E3=81=84=E3=81=9F=20komagata=20=E3=81=95=E3=82=93?= =?UTF-8?q?=E8=87=AA=E8=BA=AB=E3=82=92=E9=99=A4=E3=81=84=E3=81=9F=E4=BA=BA?= =?UTF-8?q?=E6=95=B0=E3=81=A8=E9=80=9A=E7=9F=A5=E3=81=AE=E6=95=B0=E3=81=8C?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E3=81=99=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/models/comment_notifier_for_admin_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/models/comment_notifier_for_admin_test.rb 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 From aa582eec1026196147d4b5fe2952017579a6b043 Mon Sep 17 00:00:00 2001 From: zamami Date: Wed, 17 Jun 2026 20:46:35 +0900 Subject: [PATCH 33/56] =?UTF-8?q?nespaper=E3=81=8C=E3=82=82=E3=81=86?= =?UTF-8?q?=E4=BD=BF=E3=82=8F=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=81=AE=E3=81=A7gem=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 - Gemfile.lock | 2 -- config/initializers/newspaper.rb | 5 ----- 3 files changed, 8 deletions(-) delete mode 100644 config/initializers/newspaper.rb 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/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 From 2a09a5b42a8f23c537feaf61b0596d7530a90bc6 Mon Sep 17 00:00:00 2001 From: appsu-mi Date: Sat, 7 Mar 2026 09:33:42 +0900 Subject: [PATCH 34/56] =?UTF-8?q?=E8=BF=91=E6=97=A5=E9=96=8B=E5=82=AC?= =?UTF-8?q?=E3=81=AE=E3=83=9A=E3=82=A2=E3=83=AF=E3=83=BC=E3=82=AF=E3=81=8C?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=82=BF=E3=83=BC=E3=80=81=E3=82=A2=E3=83=89?= =?UTF-8?q?=E3=83=90=E3=82=A4=E3=82=B6=E3=83=BC=E3=81=AE=E3=83=80=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=83=9C=E3=83=BC=E3=83=89=E3=81=A7=E3=82=82?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/home/_adviser_dashboard.html.slim | 2 ++ app/views/home/_mentor_dashboard.html.slim | 2 ++ 2 files changed, 4 insertions(+) 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/_mentor_dashboard.html.slim b/app/views/home/_mentor_dashboard.html.slim index 45abef7ddad..9d77a3daa70 100644 --- a/app/views/home/_mentor_dashboard.html.slim +++ b/app/views/home/_mentor_dashboard.html.slim @@ -25,6 +25,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 - if @announcements.present? From 5ea892bed80a7a2a4e7b7a76bd5645fbe5a7516c Mon Sep 17 00:00:00 2001 From: appsu-mi Date: Sat, 7 Mar 2026 09:34:01 +0900 Subject: [PATCH 35/56] =?UTF-8?q?=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E3=82=92=E6=A5=BD=E3=81=AB=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/fixtures/pair_works.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 9ad90d5190bf9051395df24f0fe62f21e8a6c4da Mon Sep 17 00:00:00 2001 From: machida Date: Fri, 1 May 2026 19:45:28 +0900 Subject: [PATCH 36/56] =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=83=80=E3=83=83=E3=82=B7=E3=83=A5=E3=83=9C=E3=83=BC=E3=83=89?= =?UTF-8?q?=E5=86=85=E3=81=AE=E3=83=9A=E3=82=A2=E3=83=AF=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E3=81=AE=E4=B8=80=E8=A6=A7=E3=81=AE=E4=BD=8D=E7=BD=AE=E3=82=92?= =?UTF-8?q?=E7=A7=BB=E5=8B=95=E3=80=81=E3=83=9A=E3=82=A2=E3=83=AF=E3=83=BC?= =?UTF-8?q?=E3=82=AF=E6=99=82=E9=96=93=E9=81=B8=E6=8A=9E=E3=81=AEdisabled?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/pair_works_helper.rb | 5 +++++ app/views/home/_mentor_dashboard.html.slim | 6 ++++-- app/views/home/_upcoming_pair_work.html.slim | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) 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/views/home/_mentor_dashboard.html.slim b/app/views/home/_mentor_dashboard.html.slim index 9d77a3daa70..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 @@ -25,8 +29,6 @@ .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 - if @announcements.present? 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 From 1b5d2c280c9dec63217d2b2d6436f2d5a8c8116f Mon Sep 17 00:00:00 2001 From: machida Date: Sat, 2 May 2026 00:26:07 +0900 Subject: [PATCH 37/56] =?UTF-8?q?meta=5Fvalue=5Fby=5Fstatus=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/helpers/pair_works_helper_test.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 From 823dfaaa26bb5b4f3681576ec568f0db89c5d810 Mon Sep 17 00:00:00 2001 From: zamami Date: Thu, 18 Jun 2026 22:23:57 +0900 Subject: [PATCH 38/56] =?UTF-8?q?=E4=BD=BF=E3=82=8F=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=AA=E3=81=84=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/regular_event.rb | 5 ----- test/models/regular_event_test.rb | 9 --------- 2 files changed, 14 deletions(-) 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/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人で主催しているイベント From d1b27d48c64ff2e8fcadad309ad07f34f7ead73e Mon Sep 17 00:00:00 2001 From: zamami Date: Thu, 18 Jun 2026 23:14:36 +0900 Subject: [PATCH 39/56] =?UTF-8?q?SQL=E4=B8=80=E5=9B=9E=E3=81=A7=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 84bee46c9d5..5a00c1f77de 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -551,12 +551,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) end def create_followup_comment(student) From cc258fb1d44c98786fd93e357fa230e479f5acb8 Mon Sep 17 00:00:00 2001 From: zamami Date: Fri, 19 Jun 2026 11:27:17 +0900 Subject: [PATCH 40/56] =?UTF-8?q?rubocop=E3=81=ABupdate=5Fall=E3=81=AE?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E3=82=92=E6=84=8F=E5=9B=B3=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E4=BC=9D=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 5a00c1f77de..b683071ecc3 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -551,7 +551,7 @@ def depressed_reports # 別Issueで入会n日目、休会開けn日目目の受講生にメッセージを送信する方針へ改修してほしい # 改修後、このメソッドは不要になると思われるので削除すること def mark_message_as_sent_for_hibernated_student - User.hibernated.update_all(sent_student_followup_message: true) + User.hibernated.update_all(sent_student_followup_message: true) # rubocop:disable Rails/SkipsModelValidations end def create_followup_comment(student) From 2485ee7acc3c6d42c460e8c5433681d257e8e832 Mon Sep 17 00:00:00 2001 From: zamami Date: Fri, 19 Jun 2026 11:43:52 +0900 Subject: [PATCH 41/56] =?UTF-8?q?update=5Fall=E3=81=A7updated=5Fat?= =?UTF-8?q?=E3=82=82=E6=9B=B4=E6=96=B0=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index b683071ecc3..ad78d90c95f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -551,7 +551,7 @@ def depressed_reports # 別Issueで入会n日目、休会開けn日目目の受講生にメッセージを送信する方針へ改修してほしい # 改修後、このメソッドは不要になると思われるので削除すること def mark_message_as_sent_for_hibernated_student - User.hibernated.update_all(sent_student_followup_message: true) # rubocop:disable Rails/SkipsModelValidations + User.hibernated.update_all(sent_student_followup_message: true, updated_at: Time.current) # rubocop:disable Rails/SkipsModelValidations end def create_followup_comment(student) From 19150552dcf7acf7d6952a9d2384219ee2e1da7d Mon Sep 17 00:00:00 2001 From: zamami Date: Sat, 20 Jun 2026 16:16:06 +0900 Subject: [PATCH 42/56] =?UTF-8?q?=E7=8F=BE=E5=9C=A8=E4=BD=BF=E3=82=8F?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=80=81watach=E3=83=AC?= =?UTF-8?q?=E3=83=B3=E3=83=80=E3=83=BC=E3=81=AB=E5=B7=AE=E3=81=97=E6=9B=BF?= =?UTF-8?q?=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/movies/_movie_header.html.slim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/movies/_movie_header.html.slim b/app/views/movies/_movie_header.html.slim index b2656eef695..4a74014cb06 100644 --- a/app/views/movies/_movie_header.html.slim +++ b/app/views/movies/_movie_header.html.slim @@ -66,8 +66,7 @@ header.page-content-header .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' From cecf1843e237a60cd623119afc353e2483087ebd Mon Sep 17 00:00:00 2001 From: zamami Date: Sat, 20 Jun 2026 17:48:13 +0900 Subject: [PATCH 43/56] =?UTF-8?q?Watch=E3=81=8C=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/system/watches_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) 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 From dfada43030aad56991c923bfbb1fd2111e2b1378 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Thu, 25 Jun 2026 11:43:18 +0900 Subject: [PATCH 44/56] =?UTF-8?q?=E3=82=B9=E3=83=86=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E7=92=B0=E5=A2=83=E3=81=A7=E5=BE=A9=E5=B8=B0?= =?UTF-8?q?=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/concerns/staging_environment.rb | 11 +++++++++++ app/models/hibernation.rb | 6 ++---- app/models/user.rb | 1 + test/models/user_test.rb | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 app/models/concerns/staging_environment.rb 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 80e0274830f..a30e7cc2b76 100644 --- a/app/models/hibernation.rb +++ b/app/models/hibernation.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Hibernation < ApplicationRecord + include StagingEnvironment + belongs_to :user validates :reason, presence: true validates :scheduled_return_on, presence: true @@ -34,10 +36,6 @@ def update_hibernated_at! user.save!(validate: false) end - def staging? - ENV['DB_NAME'] == 'bootcamp_staging' - end - def destroy_subscription! return nil if !Rails.env.production? || staging? diff --git a/app/models/user.rb b/app/models/user.rb index 353b06f6320..e9fb542dcb8 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 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) From 93a40c70b97abb4b973dcc0a2c0ac1ebe1a1d744 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Sun, 28 Jun 2026 15:50:14 +0900 Subject: [PATCH 45/56] =?UTF-8?q?pjord=E3=81=AE=E6=96=87=E7=AB=A0=E3=82=92?= =?UTF-8?q?=E8=87=AA=E7=84=B6=E3=81=AB=E3=81=99=E3=82=8B=E6=8C=87=E7=A4=BA?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/agents/pjord/agent.rb | 12 +++++++++++- test/agents/pjord/mention_response_agent_test.rb | 1 + test/agents/pjord/product_review_agent_test.rb | 1 + test/agents/pjord/question_answer_agent_test.rb | 1 + test/agents/pjord/report_comment_agent_test.rb | 1 + 5 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/agents/pjord/agent.rb b/app/agents/pjord/agent.rb index 0993bc3c1d7..1ebf6fa54f5 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/test/agents/pjord/mention_response_agent_test.rb b/test/agents/pjord/mention_response_agent_test.rb index 57d69fd210c..181d50e0a6c 100644 --- a/test/agents/pjord/mention_response_agent_test.rb +++ b/test/agents/pjord/mention_response_agent_test.rb @@ -15,6 +15,7 @@ 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, 'ピヨルドのレビューコメントに対して' diff --git a/test/agents/pjord/product_review_agent_test.rb b/test/agents/pjord/product_review_agent_test.rb index a430ab2bf44..e5dbc6ba60a 100644 --- a/test/agents/pjord/product_review_agent_test.rb +++ b/test/agents/pjord/product_review_agent_test.rb @@ -29,6 +29,7 @@ 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, 'reviewed_points には、提出物本文、URL先の内容、模範解答、過去コメントなどを確認して判断した具体的な点を1つ以上入れてください。' assert_includes chat.instructions, '管理側への説明、内部事情、運用者向けメモ、レビュー生成方針への言及は含めず' 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..b778794f8c5 100644 --- a/test/agents/pjord/report_comment_agent_test.rb +++ b/test/agents/pjord/report_comment_agent_test.rb @@ -14,6 +14,7 @@ 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.asked_message, report.title From eb87bc0c7ecdba4083cf5766d08a7f22b08743cd Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Sun, 28 Jun 2026 15:56:52 +0900 Subject: [PATCH 46/56] =?UTF-8?q?pjord=E3=81=AE=E8=A8=80=E8=AA=9E=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E8=87=AA=E7=84=B6=E3=81=AA=E6=96=87=E4=BD=93?= =?UTF-8?q?=E3=81=AB=E6=8F=83=E3=81=88=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/agents/pjord/agent.rb | 2 +- test/agents/pjord/product_review_agent_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/agents/pjord/agent.rb b/app/agents/pjord/agent.rb index 1ebf6fa54f5..a337773627e 100644 --- a/app/agents/pjord/agent.rb +++ b/app/agents/pjord/agent.rb @@ -16,7 +16,7 @@ class Pjord::Agent < RubyLLM::Agent - 回答はmarkdown形式で書く。装飾の使い方は「人間らしい文章にする」の指示に従う - 簡潔にわかりやすく答える - ユーザーが書いた言語で返答する - - 語尾に「ピヨ」など特徴的な語尾は付けず、通常の丁寧な日本語で話す + - 語尾に「ピヨ」など特徴的な語尾は付けず、その言語の自然で丁寧な文体で話す - 検索結果を踏まえたこと、ツールを使ったこと、コメントや回答を作成することなど、内部の手順説明は出力しない ## 人間らしい文章にする diff --git a/test/agents/pjord/product_review_agent_test.rb b/test/agents/pjord/product_review_agent_test.rb index e5dbc6ba60a..a3c78e74829 100644 --- a/test/agents/pjord/product_review_agent_test.rb +++ b/test/agents/pjord/product_review_agent_test.rb @@ -28,7 +28,7 @@ class Pjord::ProductReviewAgentTest < ActiveSupport::TestCase assert_includes asked_message, product.practice.submission_answer.description 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つ以上入れてください。' From da511a7d1681a430bd4b3e685efc9478a589c4f0 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Sun, 28 Jun 2026 16:19:26 +0900 Subject: [PATCH 47/56] =?UTF-8?q?pjord=E3=81=AE=E8=AA=9E=E5=B0=BE=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E6=AE=8B=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/agents/pjord/product_review_agent_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/agents/pjord/product_review_agent_test.rb b/test/agents/pjord/product_review_agent_test.rb index a3c78e74829..3d8a1a57026 100644 --- a/test/agents/pjord/product_review_agent_test.rb +++ b/test/agents/pjord/product_review_agent_test.rb @@ -28,6 +28,7 @@ class Pjord::ProductReviewAgentTest < ActiveSupport::TestCase assert_includes asked_message, product.practice.submission_answer.description 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, '提出物にレビューコメントを書いてください。' From f04ffa4f4c506d64ee7d55e4e7776a565c3b04b3 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Mon, 29 Jun 2026 16:02:21 +0900 Subject: [PATCH 48/56] =?UTF-8?q?CodePen=E3=81=AE=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92Pjord=E3=81=8C=E8=AA=AD=E3=82=81=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tools/external_content.rb | 4 +- app/tools/external_content/codepen_reader.rb | 87 +++++++++++++++++++ .../external_content/codepen_reader_test.rb | 45 ++++++++++ test/tools/external_content_tool_test.rb | 22 +++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 app/tools/external_content/codepen_reader.rb create mode 100644 test/tools/external_content/codepen_reader_test.rb diff --git a/app/tools/external_content.rb b/app/tools/external_content.rb index b05dec519e9..512829078dd 100644 --- a/app/tools/external_content.rb +++ b/app/tools/external_content.rb @@ -5,7 +5,9 @@ def self.fetch(url) uri = URI.parse(url.to_s) return 'httpまたはhttpsのURLだけ取得できます。' unless uri.is_a?(URI::HTTP) - if GithubReader.support?(uri) + if CodepenReader.support?(uri) + CodepenReader.fetch(url) + elsif GithubReader.support?(uri) GithubReader.fetch(url) else WebPageReader.fetch(url) diff --git a/app/tools/external_content/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb new file mode 100644 index 00000000000..3e4f0a4c1f1 --- /dev/null +++ b/app/tools/external_content/codepen_reader.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'uri' + +class ExternalContent::CodepenReader + CONTENT_LIMIT = 20_000 + + def self.support?(uri) + uri.is_a?(URI::HTTPS) && uri.host == 'codepen.io' && uri.path.match?(%r{\A/[^/]+/pen/[^/]+/?\z}) + end + + def self.fetch(url) + new.fetch(url) + end + + def fetch(url) + uri = URI.parse(url.to_s) + return 'CodePenの公開Pen URLだけ取得できます。' unless self.class.support?(uri) + + pen = fetch_pen(uri) + return 'CodePenを取得できませんでした。' if pen.blank? + + format_pen(uri.to_s, pen) + rescue URI::InvalidURIError + 'URLの形式が正しくありません。' + rescue StandardError => e + Rails.logger.warn("[ExternalContent::CodepenReader] #{url} #{e.class}: #{e.message}") + 'CodePenを取得できませんでした。' + end + + private + + def fetch_pen(uri) + Rails.cache.fetch("external_content/codepen/#{Digest::SHA256.hexdigest(uri.to_s)}", expires_in: 10.minutes) do + response = ExternalContent::HttpClient.get(details_url(uri), headers: request_headers) + response.success? ? JSON.parse(response.body) : nil + end + rescue JSON::ParserError + nil + end + + def details_url(uri) + owner, slug = uri.path.match(%r{\A/([^/]+)/pen/([^/]+)/?\z}).captures + "https://codepen.io/#{owner}/pen/details/#{slug}" + end + + def format_pen(url, pen) + <<~TEXT + # CodePen + - URL: #{url} + - Title: #{pen['title'].presence || '(no title)'} + - Author: #{author_name(pen)} + + ## HTML + ```html + #{limited(pen['html'])} + ``` + + ## CSS + ```css + #{limited(pen['css'])} + ``` + + ## JavaScript + ```js + #{limited(pen['js'])} + ``` + TEXT + end + + def limited(content) + content.to_s.slice(0, CONTENT_LIMIT) + end + + def author_name(pen) + author = pen['user'] || pen['owner'] + return author if author.is_a?(String) && author.present? + return author['username'] if author.is_a?(Hash) && author['username'].present? + return author['name'] if author.is_a?(Hash) && author['name'].present? + + '(unknown)' + end + + def request_headers + { 'User-Agent' => 'fjord-bootcamp-pjord', 'Accept' => 'application/json' } + end +end diff --git a/test/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb new file mode 100644 index 00000000000..1ae831e6e47 --- /dev/null +++ b/test/tools/external_content/codepen_reader_test.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'test_helper' + +class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase + setup do + Rails.cache.clear + @reader = ExternalContent::CodepenReader.new + end + + test 'fetches CodePen source code from pen details endpoint' do + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') + .with(headers: { 'Accept' => 'application/json', 'User-Agent' => 'fjord-bootcamp-pjord' }) + .to_return( + status: 200, + body: { + title: 'Sample Pen', + user: 'takafumi-yamashita', + html: '
Hello
', + css: '.demo { color: red; }', + js: 'console.log("hello")' + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') + + assert_includes result, '# CodePen' + assert_includes result, '- Title: Sample Pen' + assert_includes result, '```html' + assert_includes result, '
Hello
' + assert_includes result, '```css' + assert_includes result, '.demo { color: red; }' + assert_includes result, '```js' + assert_includes result, 'console.log("hello")' + end + + test 'rejects non CodePen URLs' do + assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://example.com/pen/WbRjEro') + end + + test 'rejects unsupported CodePen paths' do + assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://codepen.io/takafumi-yamashita/full/WbRjEro') + end +end diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index dd9bc94e42b..42dc55336f5 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -23,6 +23,28 @@ class ExternalContentToolTest < ActiveSupport::TestCase assert_not_includes result, 'ignore' end + test 'routes CodePen URLs to the CodePen reader' do + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') + .to_return( + status: 200, + body: { + title: 'Readable Pen', + user: 'takafumi-yamashita', + html: '

Readable

', + css: 'h1 { color: blue; }', + js: 'console.log("readable")' + }.to_json, + headers: { 'Content-Type' => 'application/json' } + ) + + result = @tool.execute(url: 'https://codepen.io/takafumi-yamashita/pen/WbRjEro') + + assert_includes result, '# CodePen' + assert_includes result, '

Readable

' + assert_includes result, 'h1 { color: blue; }' + assert_includes result, 'console.log("readable")' + end + test 'follows redirects' do stub_request(:get, 'https://example.com/old') .to_return(status: 302, headers: { 'Location' => '/new' }) From 5f0de8b8e798b45d4c2c510625a26f56f9c5a641 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Mon, 29 Jun 2026 16:21:10 +0900 Subject: [PATCH 49/56] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=85=88?= =?UTF-8?q?=E3=81=8C=E8=A6=8B=E3=81=88=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E3=83=A1=E3=83=B3=E3=82=BF=E3=83=BC=E3=81=AB=E5=BC=95?= =?UTF-8?q?=E3=81=8D=E7=B6=99=E3=81=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pjord/mention_response_agent/instructions.txt.erb | 3 +++ .../pjord/product_review_agent/instructions.txt.erb | 3 +++ .../pjord/report_comment_agent/instructions.txt.erb | 3 +++ app/tools/external_content.rb | 2 ++ app/tools/external_content/codepen_reader.rb | 4 ++-- app/tools/external_content/github_reader.rb | 8 ++++---- app/tools/external_content/web_page_reader.rb | 4 ++-- test/agents/pjord/mention_response_agent_test.rb | 3 +++ test/agents/pjord/product_review_agent_test.rb | 3 +++ test/agents/pjord/report_comment_agent_test.rb | 3 +++ test/tools/external_content/codepen_reader_test.rb | 7 +++++++ test/tools/external_content/github_reader_test.rb | 7 +++++++ test/tools/external_content_tool_test.rb | 4 ++-- 13 files changed, 44 insertions(+), 10 deletions(-) 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 512829078dd..e154f09d924 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/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb index 3e4f0a4c1f1..0423eb5843b 100644 --- a/app/tools/external_content/codepen_reader.rb +++ b/app/tools/external_content/codepen_reader.rb @@ -18,14 +18,14 @@ def fetch(url) return 'CodePenの公開Pen URLだけ取得できます。' unless self.class.support?(uri) pen = fetch_pen(uri) - return 'CodePenを取得できませんでした。' if pen.blank? + return ExternalContent::UNREADABLE_URL_MESSAGE if pen.blank? format_pen(uri.to_s, pen) rescue URI::InvalidURIError 'URLの形式が正しくありません。' rescue StandardError => e Rails.logger.warn("[ExternalContent::CodepenReader] #{url} #{e.class}: #{e.message}") - 'CodePenを取得できませんでした。' + ExternalContent::UNREADABLE_URL_MESSAGE end private 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/test/agents/pjord/mention_response_agent_test.rb b/test/agents/pjord/mention_response_agent_test.rb index 181d50e0a6c..1964cf75cf9 100644 --- a/test/agents/pjord/mention_response_agent_test.rb +++ b/test/agents/pjord/mention_response_agent_test.rb @@ -18,6 +18,9 @@ class Pjord::MentionResponseAgentTest < ActiveSupport::TestCase 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 3d8a1a57026..8778d13f0e5 100644 --- a/test/agents/pjord/product_review_agent_test.rb +++ b/test/agents/pjord/product_review_agent_test.rb @@ -35,6 +35,9 @@ class Pjord::ProductReviewAgentTest < ActiveSupport::TestCase 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/report_comment_agent_test.rb b/test/agents/pjord/report_comment_agent_test.rb index b778794f8c5..902835f1fce 100644 --- a/test/agents/pjord/report_comment_agent_test.rb +++ b/test/agents/pjord/report_comment_agent_test.rb @@ -17,6 +17,9 @@ class Pjord::ReportCommentAgentTest < ActiveSupport::TestCase 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/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb index 1ae831e6e47..4633ff8fe62 100644 --- a/test/tools/external_content/codepen_reader_test.rb +++ b/test/tools/external_content/codepen_reader_test.rb @@ -42,4 +42,11 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase test 'rejects unsupported CodePen paths' do assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://codepen.io/takafumi-yamashita/full/WbRjEro') end + + test 'asks Pjord to mention mentors when CodePen cannot be fetched' do + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') + .to_return(status: 403, body: 'Forbidden') + + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') + 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 42dc55336f5..a802052847a 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -64,7 +64,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 @@ -73,6 +73,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 From e960655bc35d0c5f3d2952c33b712dc585f5fc93 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Mon, 29 Jun 2026 17:06:18 +0900 Subject: [PATCH 50/56] =?UTF-8?q?CodePen=E3=81=AEraw=20URL=E3=81=8B?= =?UTF-8?q?=E3=82=89=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tools/external_content/codepen_reader.rb | 38 ++++++++++++++++- .../external_content/codepen_reader_test.rb | 42 ++++++++++++++++--- test/tools/external_content_tool_test.rb | 18 +++----- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/app/tools/external_content/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb index 0423eb5843b..03c7ed8af1d 100644 --- a/app/tools/external_content/codepen_reader.rb +++ b/app/tools/external_content/codepen_reader.rb @@ -17,7 +17,8 @@ def fetch(url) uri = URI.parse(url.to_s) return 'CodePenの公開Pen URLだけ取得できます。' unless self.class.support?(uri) - pen = fetch_pen(uri) + pen = fetch_raw_sources(uri) + pen = fetch_pen_details(uri) if pen.blank? return ExternalContent::UNREADABLE_URL_MESSAGE if pen.blank? format_pen(uri.to_s, pen) @@ -30,7 +31,30 @@ def fetch(url) private - def fetch_pen(uri) + def fetch_raw_sources(uri) + Rails.cache.fetch("external_content/codepen/raw_sources/#{Digest::SHA256.hexdigest(uri.to_s)}", expires_in: 10.minutes) do + sources = raw_source_urls(uri).filter_map do |language, source_url| + response = ExternalContent::HttpClient.get(source_url, headers: raw_source_request_headers(language)) + next unless response.success? + next if response.body.to_s.include?('Pen Not Found or Not Available as a Resource') + + [language, response.body.to_s] + end.to_h + + sources.presence + end + end + + def raw_source_urls(uri) + normalized_url = uri.to_s.delete_suffix('/') + { + 'html' => "#{normalized_url}.html", + 'css' => "#{normalized_url}.css", + 'js' => "#{normalized_url}.js" + } + end + + def fetch_pen_details(uri) Rails.cache.fetch("external_content/codepen/#{Digest::SHA256.hexdigest(uri.to_s)}", expires_in: 10.minutes) do response = ExternalContent::HttpClient.get(details_url(uri), headers: request_headers) response.success? ? JSON.parse(response.body) : nil @@ -84,4 +108,14 @@ def author_name(pen) def request_headers { 'User-Agent' => 'fjord-bootcamp-pjord', 'Accept' => 'application/json' } end + + def raw_source_request_headers(language) + content_type = { + 'html' => 'text/html', + 'css' => 'text/css', + 'js' => 'application/javascript' + }.fetch(language) + + { 'User-Agent' => 'fjord-bootcamp-pjord', 'Accept' => "#{content_type}, text/plain, */*" } + end end diff --git a/test/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb index 4633ff8fe62..89658d713e2 100644 --- a/test/tools/external_content/codepen_reader_test.rb +++ b/test/tools/external_content/codepen_reader_test.rb @@ -8,7 +8,32 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase @reader = ExternalContent::CodepenReader.new end - test 'fetches CodePen source code from pen details endpoint' do + test 'fetches CodePen source code from raw URL extensions' do + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.html') + .with(headers: { 'Accept' => 'text/html, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) + .to_return(status: 200, body: '
Hello
', headers: { 'Content-Type' => 'text/html' }) + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.css') + .with(headers: { 'Accept' => 'text/css, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) + .to_return(status: 200, body: '.demo { color: red; }', headers: { 'Content-Type' => 'text/css' }) + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.js') + .with(headers: { 'Accept' => 'application/javascript, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) + .to_return(status: 200, body: 'console.log("hello")', headers: { 'Content-Type' => 'application/javascript' }) + + result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') + + assert_includes result, '# CodePen' + assert_includes result, '- Title: (no title)' + assert_includes result, '- Author: (unknown)' + assert_includes result, '```html' + assert_includes result, '
Hello
' + assert_includes result, '```css' + assert_includes result, '.demo { color: red; }' + assert_includes result, '```js' + assert_includes result, 'console.log("hello")' + end + + test 'falls back to pen details endpoint when raw URL extensions cannot be fetched' do + stub_raw_source_requests(status: 403, body: 'Forbidden') stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') .with(headers: { 'Accept' => 'application/json', 'User-Agent' => 'fjord-bootcamp-pjord' }) .to_return( @@ -25,13 +50,10 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') - assert_includes result, '# CodePen' assert_includes result, '- Title: Sample Pen' - assert_includes result, '```html' + assert_includes result, '- Author: takafumi-yamashita' assert_includes result, '
Hello
' - assert_includes result, '```css' assert_includes result, '.demo { color: red; }' - assert_includes result, '```js' assert_includes result, 'console.log("hello")' end @@ -44,9 +66,19 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase end test 'asks Pjord to mention mentors when CodePen cannot be fetched' do + stub_raw_source_requests(status: 403, body: 'Forbidden') stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') .to_return(status: 403, body: 'Forbidden') assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') end + + private + + def stub_raw_source_requests(status:, body:) + %w[html css js].each do |extension| + stub_request(:get, "https://codepen.io/takafumi-yamashita/pen/WbRjEro.#{extension}") + .to_return(status:, body:) + end + end end diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index a802052847a..a0b57979916 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -24,18 +24,12 @@ class ExternalContentToolTest < ActiveSupport::TestCase end test 'routes CodePen URLs to the CodePen reader' do - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') - .to_return( - status: 200, - body: { - title: 'Readable Pen', - user: 'takafumi-yamashita', - html: '

Readable

', - css: 'h1 { color: blue; }', - js: 'console.log("readable")' - }.to_json, - headers: { 'Content-Type' => 'application/json' } - ) + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.html') + .to_return(status: 200, body: '

Readable

', headers: { 'Content-Type' => 'text/html' }) + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.css') + .to_return(status: 200, body: 'h1 { color: blue; }', headers: { 'Content-Type' => 'text/css' }) + stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.js') + .to_return(status: 200, body: 'console.log("readable")', headers: { 'Content-Type' => 'application/javascript' }) result = @tool.execute(url: 'https://codepen.io/takafumi-yamashita/pen/WbRjEro') From 09541097ee618e8070a804fca05e688fd8002f86 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Tue, 30 Jun 2026 01:52:05 +0900 Subject: [PATCH 51/56] =?UTF-8?q?CodePen=E3=81=AF=E5=B8=B8=E3=81=ABmentor?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=B8=E5=9B=9E=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tools/external_content/codepen_reader.rb | 103 +----------------- .../external_content/codepen_reader_test.rb | 65 +---------- test/tools/external_content_tool_test.rb | 13 +-- 3 files changed, 6 insertions(+), 175 deletions(-) diff --git a/app/tools/external_content/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb index 03c7ed8af1d..2926bb07e98 100644 --- a/app/tools/external_content/codepen_reader.rb +++ b/app/tools/external_content/codepen_reader.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true -require 'uri' - class ExternalContent::CodepenReader - CONTENT_LIMIT = 20_000 - def self.support?(uri) uri.is_a?(URI::HTTPS) && uri.host == 'codepen.io' && uri.path.match?(%r{\A/[^/]+/pen/[^/]+/?\z}) end @@ -17,105 +13,8 @@ def fetch(url) uri = URI.parse(url.to_s) return 'CodePenの公開Pen URLだけ取得できます。' unless self.class.support?(uri) - pen = fetch_raw_sources(uri) - pen = fetch_pen_details(uri) if pen.blank? - return ExternalContent::UNREADABLE_URL_MESSAGE if pen.blank? - - format_pen(uri.to_s, pen) + ExternalContent::UNREADABLE_URL_MESSAGE rescue URI::InvalidURIError 'URLの形式が正しくありません。' - rescue StandardError => e - Rails.logger.warn("[ExternalContent::CodepenReader] #{url} #{e.class}: #{e.message}") - ExternalContent::UNREADABLE_URL_MESSAGE - end - - private - - def fetch_raw_sources(uri) - Rails.cache.fetch("external_content/codepen/raw_sources/#{Digest::SHA256.hexdigest(uri.to_s)}", expires_in: 10.minutes) do - sources = raw_source_urls(uri).filter_map do |language, source_url| - response = ExternalContent::HttpClient.get(source_url, headers: raw_source_request_headers(language)) - next unless response.success? - next if response.body.to_s.include?('Pen Not Found or Not Available as a Resource') - - [language, response.body.to_s] - end.to_h - - sources.presence - end - end - - def raw_source_urls(uri) - normalized_url = uri.to_s.delete_suffix('/') - { - 'html' => "#{normalized_url}.html", - 'css' => "#{normalized_url}.css", - 'js' => "#{normalized_url}.js" - } - end - - def fetch_pen_details(uri) - Rails.cache.fetch("external_content/codepen/#{Digest::SHA256.hexdigest(uri.to_s)}", expires_in: 10.minutes) do - response = ExternalContent::HttpClient.get(details_url(uri), headers: request_headers) - response.success? ? JSON.parse(response.body) : nil - end - rescue JSON::ParserError - nil - end - - def details_url(uri) - owner, slug = uri.path.match(%r{\A/([^/]+)/pen/([^/]+)/?\z}).captures - "https://codepen.io/#{owner}/pen/details/#{slug}" - end - - def format_pen(url, pen) - <<~TEXT - # CodePen - - URL: #{url} - - Title: #{pen['title'].presence || '(no title)'} - - Author: #{author_name(pen)} - - ## HTML - ```html - #{limited(pen['html'])} - ``` - - ## CSS - ```css - #{limited(pen['css'])} - ``` - - ## JavaScript - ```js - #{limited(pen['js'])} - ``` - TEXT - end - - def limited(content) - content.to_s.slice(0, CONTENT_LIMIT) - end - - def author_name(pen) - author = pen['user'] || pen['owner'] - return author if author.is_a?(String) && author.present? - return author['username'] if author.is_a?(Hash) && author['username'].present? - return author['name'] if author.is_a?(Hash) && author['name'].present? - - '(unknown)' - end - - def request_headers - { 'User-Agent' => 'fjord-bootcamp-pjord', 'Accept' => 'application/json' } - end - - def raw_source_request_headers(language) - content_type = { - 'html' => 'text/html', - 'css' => 'text/css', - 'js' => 'application/javascript' - }.fetch(language) - - { 'User-Agent' => 'fjord-bootcamp-pjord', 'Accept' => "#{content_type}, text/plain, */*" } end end diff --git a/test/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb index 89658d713e2..76e624debd8 100644 --- a/test/tools/external_content/codepen_reader_test.rb +++ b/test/tools/external_content/codepen_reader_test.rb @@ -8,53 +8,11 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase @reader = ExternalContent::CodepenReader.new end - test 'fetches CodePen source code from raw URL extensions' do - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.html') - .with(headers: { 'Accept' => 'text/html, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) - .to_return(status: 200, body: '
Hello
', headers: { 'Content-Type' => 'text/html' }) - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.css') - .with(headers: { 'Accept' => 'text/css, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) - .to_return(status: 200, body: '.demo { color: red; }', headers: { 'Content-Type' => 'text/css' }) - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.js') - .with(headers: { 'Accept' => 'application/javascript, text/plain, */*', 'User-Agent' => 'fjord-bootcamp-pjord' }) - .to_return(status: 200, body: 'console.log("hello")', headers: { 'Content-Type' => 'application/javascript' }) - - result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') - - assert_includes result, '# CodePen' - assert_includes result, '- Title: (no title)' - assert_includes result, '- Author: (unknown)' - assert_includes result, '```html' - assert_includes result, '
Hello
' - assert_includes result, '```css' - assert_includes result, '.demo { color: red; }' - assert_includes result, '```js' - assert_includes result, 'console.log("hello")' - end - - test 'falls back to pen details endpoint when raw URL extensions cannot be fetched' do - stub_raw_source_requests(status: 403, body: 'Forbidden') - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') - .with(headers: { 'Accept' => 'application/json', 'User-Agent' => 'fjord-bootcamp-pjord' }) - .to_return( - status: 200, - body: { - title: 'Sample Pen', - user: 'takafumi-yamashita', - html: '
Hello
', - css: '.demo { color: red; }', - js: 'console.log("hello")' - }.to_json, - headers: { 'Content-Type' => 'application/json' } - ) - + test 'asks Pjord to mention mentors without fetching CodePen' do result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') - assert_includes result, '- Title: Sample Pen' - assert_includes result, '- Author: takafumi-yamashita' - assert_includes result, '
Hello
' - assert_includes result, '.demo { color: red; }' - assert_includes result, 'console.log("hello")' + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result + assert_not_requested :get, %r{\Ahttps://codepen\.io/} end test 'rejects non CodePen URLs' do @@ -64,21 +22,4 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase test 'rejects unsupported CodePen paths' do assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://codepen.io/takafumi-yamashita/full/WbRjEro') end - - test 'asks Pjord to mention mentors when CodePen cannot be fetched' do - stub_raw_source_requests(status: 403, body: 'Forbidden') - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/details/WbRjEro') - .to_return(status: 403, body: 'Forbidden') - - assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') - end - - private - - def stub_raw_source_requests(status:, body:) - %w[html css js].each do |extension| - stub_request(:get, "https://codepen.io/takafumi-yamashita/pen/WbRjEro.#{extension}") - .to_return(status:, body:) - end - end end diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index a0b57979916..27113afc592 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -24,19 +24,10 @@ class ExternalContentToolTest < ActiveSupport::TestCase end test 'routes CodePen URLs to the CodePen reader' do - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.html') - .to_return(status: 200, body: '

Readable

', headers: { 'Content-Type' => 'text/html' }) - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.css') - .to_return(status: 200, body: 'h1 { color: blue; }', headers: { 'Content-Type' => 'text/css' }) - stub_request(:get, 'https://codepen.io/takafumi-yamashita/pen/WbRjEro.js') - .to_return(status: 200, body: 'console.log("readable")', headers: { 'Content-Type' => 'application/javascript' }) - result = @tool.execute(url: 'https://codepen.io/takafumi-yamashita/pen/WbRjEro') - assert_includes result, '# CodePen' - assert_includes result, '

Readable

' - assert_includes result, 'h1 { color: blue; }' - assert_includes result, 'console.log("readable")' + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result + assert_not_requested :get, %r{\Ahttps://codepen\.io/} end test 'follows redirects' do From 3e55def41070bcf5502bc019a7af5b90f7e3e908 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Tue, 30 Jun 2026 02:01:30 +0900 Subject: [PATCH 52/56] =?UTF-8?q?=E3=83=97=E3=83=A9=E3=82=AF=E3=83=86?= =?UTF-8?q?=E3=82=A3=E3=82=B9=E3=81=94=E3=81=A8=E3=81=ABPjord=E3=83=AC?= =?UTF-8?q?=E3=83=93=E3=83=A5=E3=83=BC=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/practices_controller.rb | 1 + app/jobs/pjord_product_review_job.rb | 1 + app/models/pjord_review.rb | 1 + app/views/mentor/practices/_form.html.slim | 4 ++++ config/locales/ja.yml | 1 + ...0_add_disable_pjord_review_to_practices.rb | 7 +++++++ db/schema.rb | 3 ++- .../products/pjord_review_comment_test.rb | 19 +++++++++++++++++++ test/jobs/pjord_product_review_job_test.rb | 11 +++++++++++ test/system/practices/mentor_test.rb | 2 ++ 10 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index d491c264dd7..fa29690b6e3 100644 --- a/app/controllers/mentor/practices_controller.rb +++ b/app/controllers/mentor/practices_controller.rb @@ -55,6 +55,7 @@ def practice_params :goal, :submission, :open_product, + :disable_pjord_review, :include_progress, :completion_image, :memo, diff --git a/app/jobs/pjord_product_review_job.rb b/app/jobs/pjord_product_review_job.rb index 4fe0037fbf7..a3a80deee53 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 if product.practice.disable_pjord_review? pjord = Pjord.user return if pjord.nil? diff --git a/app/models/pjord_review.rb b/app/models/pjord_review.rb index 7bd9686d2ed..bd1179c703b 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 if product.practice.disable_pjord_review? PjordProductReviewJob.perform_later(product_id: product.id) end diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index 1f318c58798..797d41f681b 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 :disable_pjord_review, class: 'a-toggle-checkbox' + = f.label :disable_pjord_review + | ピヨルドの提出物レビューをしない場合はチェック = f.label :progress, class: 'a-form-label' .checkboxes ul.checkboxes__items diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 1e4d28dcbfc..61d9a617cc7 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -161,6 +161,7 @@ ja: goal: 修了条件 target: ターゲット submission: 提出物 + disable_pjord_review: ピヨルドの提出物レビュー submission_answer: 模範解答 progress: 進捗の計算 completion_image: 修了おめでとう画像 diff --git a/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb b/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb new file mode 100644 index 00000000000..54dbbb193fe --- /dev/null +++ b/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddDisablePjordReviewToPractices < ActiveRecord::Migration[8.0] + def change + add_column :practices, :disable_pjord_review, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 2653c7d8e5a..2879a4208ae 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" @@ -590,6 +590,7 @@ t.integer "category_id" t.datetime "created_at", precision: nil t.text "description" + t.boolean "disable_pjord_review", default: false, null: false t.text "goal" t.boolean "include_progress", default: true, null: false t.integer "last_updated_user_id" diff --git a/test/integration/products/pjord_review_comment_test.rb b/test/integration/products/pjord_review_comment_test.rb index b140bfb90c2..b30ac40f24d 100644 --- a/test/integration/products/pjord_review_comment_test.rb +++ b/test/integration/products/pjord_review_comment_test.rb @@ -27,6 +27,25 @@ class Products::PjordReviewCommentTest < ActionDispatch::IntegrationTest end end + test 'does not create product review comment by Pjord when practice disables Pjord review' do + practice = practices(:practice6) + practice.update!(disable_pjord_review: true) + + Pjord::ProductReviewAgent.stub(:review, ->(_product) { raise 'should not be called' }) do + assert_no_enqueued_jobs only: PjordProductReviewJob do + post products_path(_login_name: 'hatsuno'), + params: { practice_id: practice.id, product: { body: '提出物です。' }, commit: '提出する' } + 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..cb9142986a0 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 disables Pjord review' do + product = products(:product8) + product.practice.update!(disable_pjord_review: true) + + 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/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 6e8b0f7a81d..fa801420665 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: 'テストのメンター向けメモの内容です' + check 'practice[disable_pjord_review]', allow_label_click: true click_button '登録する' end assert_text 'プラクティスを作成しました' + assert_predicate Practice.order(:created_at).last, :disable_pjord_review? end test 'create practice as a mentor' do From 2343b36a50b8e88ede7f620e15a864998677c7e5 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Tue, 30 Jun 2026 02:10:53 +0900 Subject: [PATCH 53/56] =?UTF-8?q?Pjord=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E7=84=A1=E5=8A=B9=E6=99=82=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E6=8A=91=E6=AD=A2=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E5=BC=B7=E5=8C=96=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/integration/products/pjord_review_comment_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/products/pjord_review_comment_test.rb b/test/integration/products/pjord_review_comment_test.rb index b30ac40f24d..03ca697d847 100644 --- a/test/integration/products/pjord_review_comment_test.rb +++ b/test/integration/products/pjord_review_comment_test.rb @@ -33,8 +33,10 @@ class Products::PjordReviewCommentTest < ActionDispatch::IntegrationTest Pjord::ProductReviewAgent.stub(:review, ->(_product) { raise 'should not be called' }) do assert_no_enqueued_jobs only: PjordProductReviewJob do - post products_path(_login_name: 'hatsuno'), - params: { practice_id: practice.id, product: { body: '提出物です。' }, commit: '提出する' } + 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 From d0c43baeb269c9bca11ca3eee60b207653688108 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Tue, 30 Jun 2026 15:22:35 +0900 Subject: [PATCH 54/56] =?UTF-8?q?Pjord=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=83=95=E3=83=A9=E3=82=B0=E3=82=92=E8=82=AF=E5=AE=9A=E5=BD=A2?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/mentor/practices_controller.rb | 4 ++-- app/jobs/pjord_product_review_job.rb | 2 +- app/models/pjord_review.rb | 2 +- app/views/mentor/practices/_form.html.slim | 6 +++--- config/locales/ja.yml | 2 +- ...20260630000000_add_disable_pjord_review_to_practices.rb | 7 ------- db/migrate/20260630000000_add_pjord_review_to_practices.rb | 7 +++++++ db/schema.rb | 2 +- test/integration/products/pjord_review_comment_test.rb | 4 ++-- test/jobs/pjord_product_review_job_test.rb | 4 ++-- test/system/practices/mentor_test.rb | 4 ++-- 11 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb create mode 100644 db/migrate/20260630000000_add_pjord_review_to_practices.rb diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index fa29690b6e3..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,7 +55,7 @@ def practice_params :goal, :submission, :open_product, - :disable_pjord_review, + :pjord_review, :include_progress, :completion_image, :memo, diff --git a/app/jobs/pjord_product_review_job.rb b/app/jobs/pjord_product_review_job.rb index a3a80deee53..faefdf2e4f9 100644 --- a/app/jobs/pjord_product_review_job.rb +++ b/app/jobs/pjord_product_review_job.rb @@ -8,7 +8,7 @@ class PjordProductReviewJob < ApplicationJob def perform(product_id:) product = Product.find_by(id: product_id) return if product.nil? - return if product.practice.disable_pjord_review? + return unless product.practice.pjord_review? pjord = Pjord.user return if pjord.nil? diff --git a/app/models/pjord_review.rb b/app/models/pjord_review.rb index bd1179c703b..40ee6f7ced7 100644 --- a/app/models/pjord_review.rb +++ b/app/models/pjord_review.rb @@ -12,7 +12,7 @@ def initialize(product:, wip_before_save:) def call return unless submitted_on_save? - return if product.practice.disable_pjord_review? + return unless product.practice.pjord_review? PjordProductReviewJob.perform_later(product_id: product.id) end diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index 797d41f681b..5b764f6e404 100644 --- a/app/views/mentor/practices/_form.html.slim +++ b/app/views/mentor/practices/_form.html.slim @@ -95,9 +95,9 @@ = f.label :open_product | OKをもらっていない人も提出物を見られるようにする場合はチェック li.checkboxes__item - = f.check_box :disable_pjord_review, class: 'a-toggle-checkbox' - = f.label :disable_pjord_review - | ピヨルドの提出物レビューをしない場合はチェック + = 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/config/locales/ja.yml b/config/locales/ja.yml index 61d9a617cc7..9e3f54b9088 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -161,7 +161,7 @@ ja: goal: 修了条件 target: ターゲット submission: 提出物 - disable_pjord_review: ピヨルドの提出物レビュー + pjord_review: ピヨルドの提出物レビュー submission_answer: 模範解答 progress: 進捗の計算 completion_image: 修了おめでとう画像 diff --git a/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb b/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb deleted file mode 100644 index 54dbbb193fe..00000000000 --- a/db/migrate/20260630000000_add_disable_pjord_review_to_practices.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class AddDisablePjordReviewToPractices < ActiveRecord::Migration[8.0] - def change - add_column :practices, :disable_pjord_review, :boolean, null: false, default: false - end -end 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 2879a4208ae..17762346eb6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -590,12 +590,12 @@ t.integer "category_id" t.datetime "created_at", precision: nil t.text "description" - t.boolean "disable_pjord_review", default: false, null: false t.text "goal" t.boolean "include_progress", default: true, null: false 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/integration/products/pjord_review_comment_test.rb b/test/integration/products/pjord_review_comment_test.rb index 03ca697d847..bbe55e55f35 100644 --- a/test/integration/products/pjord_review_comment_test.rb +++ b/test/integration/products/pjord_review_comment_test.rb @@ -27,9 +27,9 @@ class Products::PjordReviewCommentTest < ActionDispatch::IntegrationTest end end - test 'does not create product review comment by Pjord when practice disables Pjord review' do + test 'does not create product review comment by Pjord when practice does not enable Pjord review' do practice = practices(:practice6) - practice.update!(disable_pjord_review: true) + practice.update!(pjord_review: false) Pjord::ProductReviewAgent.stub(:review, ->(_product) { raise 'should not be called' }) do assert_no_enqueued_jobs only: PjordProductReviewJob do diff --git a/test/jobs/pjord_product_review_job_test.rb b/test/jobs/pjord_product_review_job_test.rb index cb9142986a0..43981acc4be 100644 --- a/test/jobs/pjord_product_review_job_test.rb +++ b/test/jobs/pjord_product_review_job_test.rb @@ -25,9 +25,9 @@ class PjordProductReviewJobTest < ActiveJob::TestCase end end - test 'does nothing when practice disables Pjord review' do + test 'does nothing when practice does not enable Pjord review' do product = products(:product8) - product.practice.update!(disable_pjord_review: true) + 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 diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index fa801420665..823668207d5 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -21,11 +21,11 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' - check 'practice[disable_pjord_review]', allow_label_click: true + uncheck 'practice[pjord_review]', allow_label_click: true click_button '登録する' end assert_text 'プラクティスを作成しました' - assert_predicate Practice.order(:created_at).last, :disable_pjord_review? + assert_not_predicate Practice.order(:created_at).last, :pjord_review? end test 'create practice as a mentor' do From d4593eae6feb3d9b4fda44c7dabe19628465eb51 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Wed, 1 Jul 2026 00:44:59 +0900 Subject: [PATCH 55/56] =?UTF-8?q?http=E3=81=AECodePen=20URL=E3=82=82mentor?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=B8=E5=9B=9E=E3=81=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tools/external_content/codepen_reader.rb | 2 +- test/tools/external_content/codepen_reader_test.rb | 7 +++++++ test/tools/external_content_tool_test.rb | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/tools/external_content/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb index 2926bb07e98..ec1687a0d0c 100644 --- a/app/tools/external_content/codepen_reader.rb +++ b/app/tools/external_content/codepen_reader.rb @@ -2,7 +2,7 @@ class ExternalContent::CodepenReader def self.support?(uri) - uri.is_a?(URI::HTTPS) && uri.host == 'codepen.io' && uri.path.match?(%r{\A/[^/]+/pen/[^/]+/?\z}) + uri.is_a?(URI::HTTP) && uri.host == 'codepen.io' && uri.path.match?(%r{\A/[^/]+/pen/[^/]+/?\z}) end def self.fetch(url) diff --git a/test/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb index 76e624debd8..4afb48f6948 100644 --- a/test/tools/external_content/codepen_reader_test.rb +++ b/test/tools/external_content/codepen_reader_test.rb @@ -15,6 +15,13 @@ class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase assert_not_requested :get, %r{\Ahttps://codepen\.io/} end + test 'asks Pjord to mention mentors without fetching http CodePen' do + result = @reader.fetch('http://codepen.io/takafumi-yamashita/pen/WbRjEro') + + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result + assert_not_requested :get, %r{\Ahttp://codepen\.io/} + end + test 'rejects non CodePen URLs' do assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://example.com/pen/WbRjEro') end diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index 27113afc592..c9569c092f4 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -30,6 +30,13 @@ class ExternalContentToolTest < ActiveSupport::TestCase assert_not_requested :get, %r{\Ahttps://codepen\.io/} end + test 'routes http CodePen URLs to the CodePen reader' do + result = @tool.execute(url: 'http://codepen.io/takafumi-yamashita/pen/WbRjEro') + + assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result + assert_not_requested :get, %r{\Ahttp://codepen\.io/} + end + test 'follows redirects' do stub_request(:get, 'https://example.com/old') .to_return(status: 302, headers: { 'Location' => '/new' }) From f034a0fb1fbade95da7ea0d5d8134dc06d3a5f26 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Wed, 1 Jul 2026 03:38:57 +0900 Subject: [PATCH 56/56] =?UTF-8?q?=E5=A4=96=E9=83=A8=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AF=E5=8F=96=E5=BE=97=E5=A4=B1=E6=95=97=E6=99=82=E3=81=AE?= =?UTF-8?q?mentor=E7=A2=BA=E8=AA=8D=E3=82=92=E5=85=B1=E9=80=9A=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/tools/external_content.rb | 4 +-- app/tools/external_content/codepen_reader.rb | 20 ------------ .../external_content/codepen_reader_test.rb | 32 ------------------- test/tools/external_content_tool_test.rb | 13 +++----- 4 files changed, 5 insertions(+), 64 deletions(-) delete mode 100644 app/tools/external_content/codepen_reader.rb delete mode 100644 test/tools/external_content/codepen_reader_test.rb diff --git a/app/tools/external_content.rb b/app/tools/external_content.rb index e154f09d924..61751b35ef1 100644 --- a/app/tools/external_content.rb +++ b/app/tools/external_content.rb @@ -7,9 +7,7 @@ def self.fetch(url) uri = URI.parse(url.to_s) return 'httpまたはhttpsのURLだけ取得できます。' unless uri.is_a?(URI::HTTP) - if CodepenReader.support?(uri) - CodepenReader.fetch(url) - elsif GithubReader.support?(uri) + if GithubReader.support?(uri) GithubReader.fetch(url) else WebPageReader.fetch(url) diff --git a/app/tools/external_content/codepen_reader.rb b/app/tools/external_content/codepen_reader.rb deleted file mode 100644 index ec1687a0d0c..00000000000 --- a/app/tools/external_content/codepen_reader.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -class ExternalContent::CodepenReader - def self.support?(uri) - uri.is_a?(URI::HTTP) && uri.host == 'codepen.io' && uri.path.match?(%r{\A/[^/]+/pen/[^/]+/?\z}) - end - - def self.fetch(url) - new.fetch(url) - end - - def fetch(url) - uri = URI.parse(url.to_s) - return 'CodePenの公開Pen URLだけ取得できます。' unless self.class.support?(uri) - - ExternalContent::UNREADABLE_URL_MESSAGE - rescue URI::InvalidURIError - 'URLの形式が正しくありません。' - end -end diff --git a/test/tools/external_content/codepen_reader_test.rb b/test/tools/external_content/codepen_reader_test.rb deleted file mode 100644 index 4afb48f6948..00000000000 --- a/test/tools/external_content/codepen_reader_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'test_helper' - -class ExternalContent::CodepenReaderTest < ActiveSupport::TestCase - setup do - Rails.cache.clear - @reader = ExternalContent::CodepenReader.new - end - - test 'asks Pjord to mention mentors without fetching CodePen' do - result = @reader.fetch('https://codepen.io/takafumi-yamashita/pen/WbRjEro') - - assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result - assert_not_requested :get, %r{\Ahttps://codepen\.io/} - end - - test 'asks Pjord to mention mentors without fetching http CodePen' do - result = @reader.fetch('http://codepen.io/takafumi-yamashita/pen/WbRjEro') - - assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result - assert_not_requested :get, %r{\Ahttp://codepen\.io/} - end - - test 'rejects non CodePen URLs' do - assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://example.com/pen/WbRjEro') - end - - test 'rejects unsupported CodePen paths' do - assert_equal 'CodePenの公開Pen URLだけ取得できます。', @reader.fetch('https://codepen.io/takafumi-yamashita/full/WbRjEro') - end -end diff --git a/test/tools/external_content_tool_test.rb b/test/tools/external_content_tool_test.rb index c9569c092f4..886e29236a8 100644 --- a/test/tools/external_content_tool_test.rb +++ b/test/tools/external_content_tool_test.rb @@ -23,18 +23,13 @@ class ExternalContentToolTest < ActiveSupport::TestCase assert_not_includes result, 'ignore' end - test 'routes CodePen URLs to the CodePen reader' do - result = @tool.execute(url: 'https://codepen.io/takafumi-yamashita/pen/WbRjEro') + 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') - assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result - assert_not_requested :get, %r{\Ahttps://codepen\.io/} - end - - test 'routes http CodePen URLs to the CodePen reader' do - result = @tool.execute(url: 'http://codepen.io/takafumi-yamashita/pen/WbRjEro') + result = @tool.execute(url: 'https://example.com/unreadable') assert_equal ExternalContent::UNREADABLE_URL_MESSAGE, result - assert_not_requested :get, %r{\Ahttp://codepen\.io/} end test 'follows redirects' do