Skip to content

Curation Feature: Approval from admin required after material || event registration - #1382

Open
kennethrioja wants to merge 22 commits into
ElixirTeSS:masterfrom
kennethrioja:approval
Open

kennethrioja wants to merge 22 commits into
ElixirTeSS:masterfrom
kennethrioja:approval

Conversation

@kennethrioja

@kennethrioja kennethrioja commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary of changes

  • Generally: When material_under_admin_approval or event_under_admin_approval is set to true, and if a user registers a material or an event, the resource must be approved by either an administrator, a curator or a space admin.
  • For Registered Users:
    • When a user registers a resource, there is an info box in the form (see screenshot)
    • Once registered, they can see an icon in the /{resources} page and an 'Approval status: requested' in the {resources}/{name} page (see screenshots)
    • The resource is hidden from unlogged users (both UI and JSON-LD)
  • For Admins:
    • To curate, they can go to the /{resources} page, where a Curate {resource} is found next to the Register button OR they can find this page in the user dropdown (see screenshots)
    • Once in the /curate/resources page, they can see by default the list of Requested materials. You can filter by Type, User, Provider, Space and bulk_approve or reject the filtered list of resources to avoid doing it 1 by 1. A Reset filter button is also present. A list of recent curation activity is found on the right.
    • Private resources can be shown in this dashboard for admin and for private space admins (a curator can thus see it only if they are the private space admin)
  • Ingested materials:
    • When we ingest a resource for the first time, they are automatically set to :requested (see material.rb#set_approval_status, same for events)
    • When updated, they come back to :requested
  • For Space admins:
    • They can curate resources only from the space they are admin
    • If a space is private, they can view them via the curation dashboard
  • For Curators:
    • They have the same approval rights than admin in public spaces except that they cannot see/curate :requested private space resources unless they are space admins
  • Devs:
    • app/policies/application_policy.rb#shown? is the place to manage if a resource can be shown to a user. Then all the other resource-specific policies can be found in the related {resource}_policy.rb file. I avoided having to put conditions such as is_admin? in the html.erb files, this is why I tried to have as much conditions with <% if policy(@event).{update|approve}? %> as possible, so that everything is managed in the policies rather that in the views.

Motivation and context

Curation is needed in HEP and EVERSE Training

Screenshots

Users view

mat_alert_new mat_requested_icon mat_requested_view

Admin/Curator/Space admin curation buttons

curate_button mat_admin_index

Curation page

curate_resources

Not covered in this PR, but could be useful

  • Emails to the admins/curators/space admins to say, "there are n resources to be curated".
  • Say why a resource has been rejected -> and if too much spam after several refusals, incremental cooldown days to register a material
  • If a resource is not approved, its URL can still checked in the form (see check_exists)

Checklist

  • I have read and followed the CONTRIBUTING guide.
  • I confirm that I have the authority necessary to make this contribution on behalf of its copyright owner and agree to license it to the TeSS codebase under the BSD license.

@kennethrioja

Copy link
Copy Markdown
Contributor Author

User documentation to add!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comment thread app/controllers/events_controller.rb Outdated
external_resources: %i[url title], material_ids: [],
llm_interaction_attributes: %i[id scrape_or_process model prompt input output needs_processing _destroy],
locked_fields: [], instructors: [:name, :orcid], contributors: [:name, :orcid]]
permitted << :approval_status if User.current_user&.can_approve_resources?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added :approval_status parameter to both #create and #update methods by checking policy(record).approve?. We now pass record to event_params to be able to call policy(record).approve?

Comment thread app/controllers/materials_controller.rb Outdated
external_resources_attributes: %i[id url title _destroy],
external_resources: %i[url title],
event_ids: [], locked_fields: []]
permitted << :approval_status if User.current_user&.can_approve_resources?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same than previous comment

Comment thread app/models/concerns/searchable.rb Outdated
Comment on lines +141 to +150
# Filters by approval_status for unauthenticated requests (regardless of whether the feature is enabled or disabled)
if name == 'Material' && TeSS::Config.feature['material_under_admin_approval'] && !(user&.is_admin? || user&.is_curator?)
any_of do
with(:approval_status, I18n.t('materials.approval_status.approved'))
with(:user_id, user.id) if user.present?
end
end

# Filters by approval_status for unauthenticated requests (regardless of whether the feature is enabled or disabled)
if name == 'Event' && TeSS::Config.feature['event_under_admin_approval'] && !(user&.is_admin? || user&.is_curator?)

@kennethrioja kennethrioja Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added Scope#resolved in both material and event policies (and includes owner/admin/curator/space admin exceptions)

Comment on lines +3 to +10
add_column :materials, :approval_status, :string, default: 'approved'
add_column :events, :approval_status, :string, default: 'approved'

reversible do |dir|
dir.up do
# Backfill existing records (will be NULL on existing rows when adding a column)
Material.unscoped.where(approval_status: nil).update_all(approval_status: 'approved')
Event.unscoped.where(approval_status: nil).update_all(approval_status: 'approved')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accepted the suggestion

Comment thread app/controllers/curator_controller.rb Outdated
@status = params[:status].presence || 'requested'
@type = params[:type].presence || 'all'

target_classes = @type == 'all' ? CURATABLE_TYPES.values : [CURATABLE_TYPES[@type]].compact

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Tailored target_classes to filter on the enabled_curatable_classes

Comment thread app/views/events/show.html.erb Outdated
<%= resource_approval_badge(@event.approval_status) %>
</p>

<% if !@event.not_approved? && !@event.approval_requested? && !@event.approved? && !policy(@event).approve? %>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I deleted it, users never ask for request approval, it is automatically done – this is a remnant from my previous design

Comment thread app/views/materials/show.html.erb Outdated
<%= resource_approval_badge(@material.approval_status) %>
</p>

<% if !@material.not_approved? && !@material.approval_requested? && !@material.approved? && !policy(@material).approve? %>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I deleted it, users never ask for request approval, it is automatically done – this is a remnant from my previous design

Comment thread config/routes.rb Outdated
Comment on lines +163 to +164
get 'curate/materials' => 'curator#materials', as: :curate_materials
get 'curate/events' => 'curator#events', as: :curate_events

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggestion accepted

Comment thread test/models/event_test.rb Outdated
Comment on lines +910 to +914
# test '.approved scope returns only approved event' do
# approved = Event.approved
# assert_includes approved, @approved_event
# refute_includes approved, @not_approved_event
# refute_includes approved, @requested_event

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +89 to +94
context = Struct.new(:user).new(@private_space_owner)
scope = ApplicationPolicy::Scope.new(context, Material).resolve_curatable

scope.each do |material|
assert_equal @space.id, material.space_id
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Suggestion accepted

… space but cannot see and curate requested private resources

Using Copilot, GPT-6 Luna, 6.65 AIC used
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants