From b72bd9038aa6a17f1ac14c1acaaf51818b2b296d Mon Sep 17 00:00:00 2001
From: kennethrioja <59597207+kennethrioja@users.noreply.github.com>
Date: Tue, 6 Jan 2026 14:15:46 +0100
Subject: [PATCH 1/2] feat(registrations): frontend and json-ld of
registrations in both user and trainer page
---
app/controllers/trainers_controller.rb | 11 ++-
app/controllers/users_controller.rb | 8 ++
app/views/common/_registrations_list.html.erb | 96 +++++++++++++++++++
app/views/trainers/show.html.erb | 14 +++
app/views/users/show.html.erb | 95 +-----------------
test/controllers/trainers_controller_test.rb | 43 +++++++++
test/controllers/users_controller_test.rb | 39 ++++++++
7 files changed, 211 insertions(+), 95 deletions(-)
create mode 100644 app/views/common/_registrations_list.html.erb
diff --git a/app/controllers/trainers_controller.rb b/app/controllers/trainers_controller.rb
index 076086703..3ad1f42cf 100644
--- a/app/controllers/trainers_controller.rb
+++ b/app/controllers/trainers_controller.rb
@@ -24,10 +24,11 @@ def index
# GET /trainers/1.json
def show
+ @bioschemas = registrations.flat_map(&:to_bioschemas)
respond_to do |format|
+ format.html
format.json
format.json_api { render json: @trainer }
- format.html
end
end
@@ -46,4 +47,10 @@ def trainer_params
{ :fields => [] }, { :social_media => [] })
end
-end
\ No newline at end of file
+ # Get trainer's registrations
+ def registrations
+ events = @trainer.user.events.in_current_space
+ materials = @trainer.user.materials.in_current_space
+ events + materials
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 36a2e73f9..cb78f9ca4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -39,6 +39,7 @@ def invitees
# GET /users/1
# GET /users/1.json
def show
+ @bioschemas = registrations.flat_map(&:to_bioschemas)
respond_to do |format|
format.html
format.json
@@ -151,4 +152,11 @@ def check_profile_id
handle_error(:forbidden, 'Invalid profile ID.')
end
end
+
+ # Get user's registrations
+ def registrations
+ events = @user.events.in_current_space
+ materials = @user.materials.in_current_space
+ events + materials
+ end
end
diff --git a/app/views/common/_registrations_list.html.erb b/app/views/common/_registrations_list.html.erb
new file mode 100644
index 000000000..b17aad566
--- /dev/null
+++ b/app/views/common/_registrations_list.html.erb
@@ -0,0 +1,96 @@
+<% resource_limit = UsersHelper.user_profile_resource_limit %>
+
+ List of registrations in <%= TeSS::Config.site['title_short'] %>
+
+
+ <% materials = user.materials.in_current_space.limit(resource_limit) %>
+ <% materials_count = user.materials.in_current_space.count %>
+ <% upcoming_events = user.events.in_current_space.not_finished %>
+ <% past_events = user.events.in_current_space.finished %>
+ <% events = upcoming_events.limit(resource_limit) %>
+ <% e = user.events.in_current_space.from_verified_users.not_disabled %>
+ <% events_count = e.count %>
+ <% not_finished_events_count = e.not_finished.count %>
+ <% workflows = user.workflows.in_current_space.visible_by(current_user).limit(resource_limit) %>
+ <% workflows_count = user.workflows.in_current_space.visible_by(current_user).count %>
+ <% collections = user.collections.in_current_space.visible_by(current_user).limit(resource_limit) %>
+ <% collections_count = user.collections.in_current_space.visible_by(current_user).count %>
+ <% activator = tab_activator %>
+
+
+
+ <% if TeSS::Config.feature['events'] %>
+ <%= tab('Events', icon_class_for_model('events'), 'events', activator: activator,
+ disabled: { check: events_count.zero?, message: 'No associated events' },
+ count: not_finished_events_count.zero? && events_count.positive? ? '0*' : not_finished_events_count) %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['materials'] %>
+ <%= tab('Materials', icon_class_for_model('materials'), 'materials', activator: activator,
+ disabled: { check: materials.none?, message: 'No registered training materials' },
+ count: materials_count) %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['collections'] %>
+ <%= tab('Collections', icon_class_for_model('collections'), 'collections', activator: activator,
+ disabled: { check: collections.none?, message: 'No registered collections' },
+ count: collections_count) %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['workflows'] %>
+ <%= tab('Workflows', icon_class_for_model('workflows'), 'workflows', activator: activator,
+ disabled: { check: workflows.none?, message: 'No workflows' },
+ count: workflows_count) %>
+ <% end %>
+
+
+
+
+ <% if TeSS::Config.feature['events'] %>
+ <%= render partial: 'common/associated_events',
+ locals: { total_count: upcoming_events.count,
+ past_count: past_events.count,
+ limit: resource_limit,
+ resources: events,
+ activator: activator,
+ view_all_link: events_path(user: user.username),
+ inc_expired_link: events_path(user: user.username, include_expired: true) } %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['materials'] %>
+ <%= render partial: 'common/associated_resources',
+ locals: { model: Material,
+ total_count: materials_count,
+ limit: resource_limit,
+ resources: materials,
+ activator: activator,
+ view_all_link: materials_path(user: user.username) } %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['collections'] %>
+ <%= render partial: 'common/associated_resources',
+ locals: { model: Collection,
+ total_count: collections_count,
+ limit: resource_limit,
+ resources: collections,
+ activator: activator,
+ view_all_link: collections_path(user: user.username) } %>
+ <% end %>
+
+
+ <% if TeSS::Config.feature['workflows'] %>
+ <%= render partial: 'common/associated_resources',
+ locals: { model: Workflow,
+ total_count: workflows_count,
+ limit: resource_limit,
+ resources: workflows,
+ activator: activator,
+ view_all_link: workflows_path(user: user.username) } %>
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/trainers/show.html.erb b/app/views/trainers/show.html.erb
index c9d5ad2b8..b5d9a9b0c 100644
--- a/app/views/trainers/show.html.erb
+++ b/app/views/trainers/show.html.erb
@@ -1,3 +1,8 @@
+<% materials_count = @trainer.user.materials.in_current_space.count %>
+<% events_count = @trainer.user.events.in_current_space.from_verified_users.not_disabled.count %>
+<% workflows_count = @trainer.user.workflows.in_current_space.visible_by(current_user).count %>
+<% collections_count = @trainer.user.collections.in_current_space.visible_by(current_user).count %>
+<% total = materials_count.to_i + events_count.to_i + workflows_count.to_i + collections_count.to_i %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index c3424da77..197eb4a36 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -1,4 +1,3 @@
-<% resource_limit = UsersHelper.user_profile_resource_limit %>
<%- model_class = Profile -%>
@@ -128,97 +127,7 @@
<%= render(partial: 'users/partials/ban', locals: { ban: @user.ban }) if @user.banned? && current_user.try(:is_admin?) %>
-
- <% materials = @user.materials.in_current_space.limit(resource_limit) %>
- <% materials_count = @user.materials.in_current_space.count %>
- <% upcoming_events = @user.events.in_current_space.not_finished %>
- <% past_events = @user.events.in_current_space.finished %>
- <% events = upcoming_events.limit(resource_limit) %>
- <% e = @user.events.in_current_space.from_verified_users.not_disabled %>
- <% events_count = e.count %>
- <% not_finished_events_count = e.not_finished.count %>
- <% workflows = @user.workflows.in_current_space.visible_by(current_user).limit(resource_limit) %>
- <% workflows_count = @user.workflows.in_current_space.visible_by(current_user).count %>
- <% collections = @user.collections.in_current_space.visible_by(current_user).limit(resource_limit) %>
- <% collections_count = @user.collections.in_current_space.visible_by(current_user).count %>
- <% activator = tab_activator %>
-
-
-
- <% if TeSS::Config.feature['events'] %>
- <%= tab('Events', icon_class_for_model('events'), 'events', activator: activator,
- disabled: { check: events_count.zero?, message: 'No associated events' },
- count: not_finished_events_count.zero? && events_count.positive? ? '0*' : not_finished_events_count) %>
- <% end %>
-
-
- <% if TeSS::Config.feature['materials'] %>
- <%= tab('Materials', icon_class_for_model('materials'), 'materials', activator: activator,
- disabled: { check: materials.none?, message: 'No registered training materials' },
- count: materials_count) %>
- <% end %>
-
-
- <% if TeSS::Config.feature['collections'] %>
- <%= tab('Collections', icon_class_for_model('collections'), 'collections', activator: activator,
- disabled: { check: collections.none?, message: 'No registered collections' },
- count: collections_count) %>
- <% end %>
-
-
- <% if TeSS::Config.feature['workflows'] %>
- <%= tab('Workflows', icon_class_for_model('workflows'), 'workflows', activator: activator,
- disabled: { check: workflows.none?, message: 'No workflows' },
- count: workflows_count) %>
- <% end %>
-
-
-
-
- <% if TeSS::Config.feature['events'] %>
- <%= render partial: 'common/associated_events',
- locals: { total_count: upcoming_events.count,
- past_count: past_events.count,
- limit: resource_limit,
- resources: events,
- activator: activator,
- view_all_link: events_path(user: @user.username),
- inc_expired_link: events_path(user: @user.username, include_expired: true) } %>
- <% end %>
-
-
- <% if TeSS::Config.feature['materials'] %>
- <%= render partial: 'common/associated_resources',
- locals: { model: Material,
- total_count: materials_count,
- limit: resource_limit,
- resources: materials,
- activator: activator,
- view_all_link: materials_path(user: @user.username) } %>
- <% end %>
-
-
- <% if TeSS::Config.feature['collections'] %>
- <%= render partial: 'common/associated_resources',
- locals: { model: Collection,
- total_count: collections_count,
- limit: resource_limit,
- resources: collections,
- activator: activator,
- view_all_link: collections_path(user: @user.username) } %>
- <% end %>
-
-
- <% if TeSS::Config.feature['workflows'] %>
- <%= render partial: 'common/associated_resources',
- locals: { model: Workflow,
- total_count: workflows_count,
- limit: resource_limit,
- resources: workflows,
- activator: activator,
- view_all_link: workflows_path(user: @user.username) } %>
- <% end %>
-
-
+
+ <%= render(partial: 'common/registrations_list', locals: { user: @user }) %>
diff --git a/test/controllers/trainers_controller_test.rb b/test/controllers/trainers_controller_test.rb
index 50e723c8b..8a7508d2a 100644
--- a/test/controllers/trainers_controller_test.rb
+++ b/test/controllers/trainers_controller_test.rb
@@ -3,6 +3,11 @@
class TrainersControllerTest < ActionController::TestCase
include Devise::Test::ControllerHelpers
+ setup do
+ @user = users(:trainer_user)
+ @trainer = @user.profile
+ end
+
test 'get all trainers' do
get :index
assert_response :success
@@ -12,4 +17,42 @@ class TrainersControllerTest < ActionController::TestCase
assert_includes trainers, users(:trainer_user).profile
end
+ test 'should show material and event in trainer page as bioschemas JSON-LD' do
+ material = materials(:good_material)
+ material.user_id = @user.id
+ material.scientific_topic_uris = ['http://edamontology.org/topic_0654']
+ material.save!
+
+ event = events(:one)
+ event.user = @user
+ event.scientific_topic_uris = ['http://edamontology.org/topic_0654']
+ event.save!
+
+ get :show, params: { id: @trainer, format: :html }
+ assert_response :success
+ assert assigns(:trainer)
+
+ doc = Nokogiri::HTML(response.body)
+ jsonld = doc
+ .css('script[type="application/ld+json"]')
+ .map { |s| JSON.parse(s.text) rescue nil }
+ .compact
+
+ json_material = jsonld.find { |entry| entry['name'] == material.title }
+ assert_equal material.title, json_material['name']
+ assert_equal 'http://schema.org', json_material['@context']
+ assert_equal 'LearningResource', json_material['@type']
+ assert_equal 'https://bioschemas.org/profiles/TrainingMaterial/1.0-RELEASE', json_material['dct:conformsTo']['@id']
+ assert_equal material.url, json_material['url']
+ assert_equal material.scientific_topic_uris.first, json_material['about'].first['@id']
+
+ json_event = jsonld.find { |entry| entry['name'] == event.title }
+ assert_equal 'http://schema.org', json_event['@context']
+ assert_equal 'Course', json_event['@type']
+ assert_equal 'https://bioschemas.org/profiles/Course/1.0-RELEASE', json_event['dct:conformsTo']['@id']
+ assert_equal event.title, json_event['name']
+ assert_equal event.url, json_event['url']
+ assert_equal event.scientific_topic_uris.first, json_event['about'].first['@id']
+ assert_equal event.external_resources.first.url, json_event['mentions'].first['url']
+ end
end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 64e80f0bb..11e05ff71 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -525,4 +525,43 @@ class UsersControllerTest < ActionController::TestCase
assert_response :success
assert_select '#sidebar button', text: 'Authenticate your ORCID', count: 0
end
+
+ test 'should show material and event in trainer page as bioschemas JSON-LD' do
+ material = materials(:good_material)
+ material.user_id = @user.id
+ material.scientific_topic_uris = ['http://edamontology.org/topic_0654']
+ material.save!
+
+ event = events(:one)
+ event.user = @user
+ event.scientific_topic_uris = ['http://edamontology.org/topic_0654']
+ event.save!
+
+ get :show, params: { id: @user, format: :html }
+ assert_response :success
+ assert assigns(:user)
+
+ doc = Nokogiri::HTML(response.body)
+ jsonld = doc
+ .css('script[type="application/ld+json"]')
+ .map { |s| JSON.parse(s.text) rescue nil }
+ .compact
+
+ json_material = jsonld.find { |entry| entry['name'] == material.title }
+ assert_equal material.title, json_material['name']
+ assert_equal 'http://schema.org', json_material['@context']
+ assert_equal 'LearningResource', json_material['@type']
+ assert_equal 'https://bioschemas.org/profiles/TrainingMaterial/1.0-RELEASE', json_material['dct:conformsTo']['@id']
+ assert_equal material.url, json_material['url']
+ assert_equal material.scientific_topic_uris.first, json_material['about'].first['@id']
+
+ json_event = jsonld.find { |entry| entry['name'] == event.title }
+ assert_equal 'http://schema.org', json_event['@context']
+ assert_equal 'Course', json_event['@type']
+ assert_equal 'https://bioschemas.org/profiles/Course/1.0-RELEASE', json_event['dct:conformsTo']['@id']
+ assert_equal event.title, json_event['name']
+ assert_equal event.url, json_event['url']
+ assert_equal event.scientific_topic_uris.first, json_event['about'].first['@id']
+ assert_equal event.external_resources.first.url, json_event['mentions'].first['url']
+ end
end
From 2f986313e5a38a586b53c6d897d5926819e33948 Mon Sep 17 00:00:00 2001
From: kennethrioja <59597207+kennethrioja@users.noreply.github.com>
Date: Wed, 28 Jan 2026 12:53:59 +0100
Subject: [PATCH 2/2] review(registrations): copilot 10 comments #1212
---
app/controllers/trainers_controller.rb | 9 +--------
app/controllers/users_controller.rb | 9 +--------
app/models/user.rb | 7 +++++++
app/views/common/_registrations_list.html.erb | 6 ++++++
app/views/trainers/show.html.erb | 13 +------------
test/controllers/trainers_controller_test.rb | 14 ++++++++++----
test/controllers/users_controller_test.rb | 14 ++++++++++----
7 files changed, 36 insertions(+), 36 deletions(-)
diff --git a/app/controllers/trainers_controller.rb b/app/controllers/trainers_controller.rb
index 3ad1f42cf..34619d1c6 100644
--- a/app/controllers/trainers_controller.rb
+++ b/app/controllers/trainers_controller.rb
@@ -24,7 +24,7 @@ def index
# GET /trainers/1.json
def show
- @bioschemas = registrations.flat_map(&:to_bioschemas)
+ @bioschemas = @trainer.user.registrations.flat_map(&:to_bioschemas)
respond_to do |format|
format.html
format.json
@@ -46,11 +46,4 @@ def trainer_params
{ :expertise_technical => [] }, { :interest => [] }, { :activity => [] },
{ :fields => [] }, { :social_media => [] })
end
-
- # Get trainer's registrations
- def registrations
- events = @trainer.user.events.in_current_space
- materials = @trainer.user.materials.in_current_space
- events + materials
- end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index cb78f9ca4..336da70bf 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -39,7 +39,7 @@ def invitees
# GET /users/1
# GET /users/1.json
def show
- @bioschemas = registrations.flat_map(&:to_bioschemas)
+ @bioschemas = @user.registrations.flat_map(&:to_bioschemas)
respond_to do |format|
format.html
format.json
@@ -152,11 +152,4 @@ def check_profile_id
handle_error(:forbidden, 'Invalid profile ID.')
end
end
-
- # Get user's registrations
- def registrations
- events = @user.events.in_current_space
- materials = @user.materials.in_current_space
- events + materials
- end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index dfb7df1a4..167194103 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -385,6 +385,13 @@ def has_role_in_any_space?(role)
space_roles.where(key: role).any?
end
+ # Get user's registrations
+ def registrations
+ n_events = events.in_current_space
+ n_materials = materials.in_current_space
+ n_events + n_materials
+ end
+
protected
def reassign_resources(new_owner = User.get_default_user)
diff --git a/app/views/common/_registrations_list.html.erb b/app/views/common/_registrations_list.html.erb
index b17aad566..36588b736 100644
--- a/app/views/common/_registrations_list.html.erb
+++ b/app/views/common/_registrations_list.html.erb
@@ -16,6 +16,12 @@
<% collections = user.collections.in_current_space.visible_by(current_user).limit(resource_limit) %>
<% collections_count = user.collections.in_current_space.visible_by(current_user).count %>
<% activator = tab_activator %>
+ <% total = materials_count.to_i + events_count.to_i + workflows_count.to_i + collections_count.to_i %>
+
+
+ <% if total.positive? %>
+ Added a total of <%= total %> training resources in <%= TeSS::Config.site['title_short'] %>.
+ <% end %>
diff --git a/app/views/trainers/show.html.erb b/app/views/trainers/show.html.erb
index b5d9a9b0c..f4627a69d 100644
--- a/app/views/trainers/show.html.erb
+++ b/app/views/trainers/show.html.erb
@@ -1,8 +1,3 @@
-<% materials_count = @trainer.user.materials.in_current_space.count %>
-<% events_count = @trainer.user.events.in_current_space.from_verified_users.not_disabled.count %>
-<% workflows_count = @trainer.user.workflows.in_current_space.visible_by(current_user).count %>
-<% collections_count = @trainer.user.collections.in_current_space.visible_by(current_user).count %>
-<% total = materials_count.to_i + events_count.to_i + workflows_count.to_i + collections_count.to_i %>
diff --git a/test/controllers/trainers_controller_test.rb b/test/controllers/trainers_controller_test.rb
index 8a7508d2a..e1063d8b5 100644
--- a/test/controllers/trainers_controller_test.rb
+++ b/test/controllers/trainers_controller_test.rb
@@ -34,9 +34,13 @@ class TrainersControllerTest < ActionController::TestCase
doc = Nokogiri::HTML(response.body)
jsonld = doc
- .css('script[type="application/ld+json"]')
- .map { |s| JSON.parse(s.text) rescue nil }
- .compact
+ .css('script[type="application/ld+json"]')
+ .map do |s|
+ JSON.parse(s.text)
+ rescue JSON::ParserError
+ nil
+ end
+ .compact
json_material = jsonld.find { |entry| entry['name'] == material.title }
assert_equal material.title, json_material['name']
@@ -53,6 +57,8 @@ class TrainersControllerTest < ActionController::TestCase
assert_equal event.title, json_event['name']
assert_equal event.url, json_event['url']
assert_equal event.scientific_topic_uris.first, json_event['about'].first['@id']
- assert_equal event.external_resources.first.url, json_event['mentions'].first['url']
+ external_resource = event.external_resources.first
+ assert_not_nil external_resource
+ assert_equal external_resource.url, json_event['mentions'].first['url']
end
end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
index 11e05ff71..2d16af748 100644
--- a/test/controllers/users_controller_test.rb
+++ b/test/controllers/users_controller_test.rb
@@ -543,9 +543,13 @@ class UsersControllerTest < ActionController::TestCase
doc = Nokogiri::HTML(response.body)
jsonld = doc
- .css('script[type="application/ld+json"]')
- .map { |s| JSON.parse(s.text) rescue nil }
- .compact
+ .css('script[type="application/ld+json"]')
+ .map do |s|
+ JSON.parse(s.text)
+ rescue JSON::ParserError
+ nil
+ end
+ .compact
json_material = jsonld.find { |entry| entry['name'] == material.title }
assert_equal material.title, json_material['name']
@@ -562,6 +566,8 @@ class UsersControllerTest < ActionController::TestCase
assert_equal event.title, json_event['name']
assert_equal event.url, json_event['url']
assert_equal event.scientific_topic_uris.first, json_event['about'].first['@id']
- assert_equal event.external_resources.first.url, json_event['mentions'].first['url']
+ external_resource = event.external_resources.first
+ assert_not_nil external_resource
+ assert_equal external_resource.url, json_event['mentions'].first['url']
end
end