Skip to content
2 changes: 1 addition & 1 deletion app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def set_organization
:organization_status, :windows_type, :addresses,
:categorizable_items,
{ comments: [ :created_by, :updated_by ] },
affiliations: { person: [ :user, *Organization::PEOPLE_TAGGINGS ] }
affiliations: { person: [ :user, :affiliations, *Organization::PEOPLE_TAGGINGS ] }
).find(params[:id])
end

Expand Down
9 changes: 9 additions & 0 deletions app/decorators/organization_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ def badges
badges
end

# Keeps every active title per person so a facilitator's card also lists e.g. "Director".
def active_affiliations_by_facilitator
object.affiliations
.select { |affiliation| affiliation.active? && affiliation.person }
.sort_by { |affiliation| [ affiliation.person.first_name.to_s.downcase, affiliation.person.last_name.to_s.downcase ] }
.group_by(&:person)
.select { |_person, affiliations| affiliations.any?(&:facilitator?) }
end

private

# The facilitator rows that record real facilitation. A zero-length (start ==
Expand Down
4 changes: 3 additions & 1 deletion app/views/bookmarks/_personal_row.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
<div class="text-sm text-gray-600">
<% author = bookmarkable.created_by if bookmarkable.respond_to?(:created_by) %>
Created by:
<% if author&.person %>
<% if author&.person && allowed_to?(:show?, author.person) %>
<%= person_profile_button(author.person, compact: true, width_class: "inline-flex w-fit", data: { turbo: false }) %>
<% elsif author&.person %>
<span class="font-medium text-gray-700"><%= person_name(author.person) %></span>
<% elsif author %>
<%= user_button(author, data: { turbo: false }) %>
<% elsif bookmark.bookmarkable_type == "Workshop" %>
Expand Down
17 changes: 10 additions & 7 deletions app/views/organizations/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,27 @@
<div class="grid grid-cols-1 gap-8 md:grid-cols-5">
<!-- Organization affiliations -->
<div id="affiliations" class="scroll-mt-4 md:col-span-3" data-controller="paginated-fields" data-paginated-fields-per-page-value="9">
<h2 class="mb-3 font-display text-2xl text-gray-900">Affiliations</h2>
<% active_affiliations = @organization.affiliations
.select { |a| !a.inactive? && (a.end_date.nil? || a.end_date >= Date.current) && a.person.present? }
.sort_by { |a| [a.person.first_name.to_s.downcase, a.person.last_name.to_s.downcase] } %>
<% grouped = active_affiliations.group_by(&:person) %>
<h2 class="mb-3 font-display text-2xl text-gray-900">Facilitators</h2>
<% grouped = org_decorated.active_affiliations_by_facilitator.select { |person, _| allowed_to?(:show?, person) } %>
<% if grouped.any? %>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
<% grouped.each do |person, person_affiliations| %>
<div data-paginated-fields-target="item">
<div class="relative" data-paginated-fields-target="item">
<% titles = person_affiliations.filter_map { |a| a.title.presence }.uniq %>
<% titles = titles.sort_by { |t| [t.downcase.include?("facilitator") ? 0 : 1, t.downcase] } %>
<%= person_profile_button(person, subtitle: titles.any? ? titles.join(", ") : nil) %>
<% if current_user&.super_user? && !person.published? %>
<span class="absolute top-1 right-1 z-10 flex h-5 w-5 items-center justify-center rounded-full border border-blue-200 bg-blue-100 text-blue-700 shadow-sm"
title="Hidden from facilitators β€” only admins see this card" data-testid="profile-hidden-lock">
<i class="fa-solid fa-lock text-2xs"></i>
</span>
<% end %>
</div>
<% end %>
</div>
<div data-paginated-fields-target="nav" class="mt-3"></div>
<% else %>
<p class="text-gray-500 italic">No active affiliations.</p>
<p class="text-gray-500 italic">No active facilitators.</p>
<% end %>
</div>
<!-- Sectors + Age groups served (two columns on desktop; stack on narrow viewports) -->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: "Organization pages list active facilitators"
area: people
display_status: user_facing
released_on: 2026-10-22
pr_number: 2785
summary: >-
An organization page now lists its active facilitators, and facilitators see only the ones whose
profiles they can open, so every name leads to a profile.
pro_tips:
- "A facilitator's card still shows their other titles at the organization, like Director."
- "Admins see every active facilitator; a lock marks the ones hidden from facilitators, who don't see those cards."
- "On My bookmarks, a creator whose profile you can't open shows as a plain name."
20 changes: 20 additions & 0 deletions spec/decorators/organization_decorator_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
require "rails_helper"

RSpec.describe OrganizationDecorator do
describe "#active_affiliations_by_facilitator" do
let(:organization) { create(:organization) }
let(:facilitator) { create(:person, first_name: "Zoe") }
let(:earlier) { create(:person, first_name: "Abe") }

it "groups each active facilitator's active titles, sorted by name, leaving out everyone else" do
facilitation = create(:affiliation, organization: organization, person: facilitator)
director = create(:affiliation, organization: organization, person: facilitator, title: "Director")
create(:affiliation, organization: organization, person: facilitator, title: "Counselor", end_date: 1.month.ago)
create(:affiliation, organization: organization, person: earlier)
create(:affiliation, organization: organization, title: "Board Member")
create(:affiliation, organization: organization, end_date: 1.month.ago)

grouped = organization.reload.decorate.active_affiliations_by_facilitator

expect(grouped.keys).to eq([ earlier, facilitator ])
expect(grouped[facilitator]).to contain_exactly(facilitation, director)
end
end

describe "#affiliated_since_display" do
let(:organization) { create(:organization) }

Expand Down
21 changes: 21 additions & 0 deletions spec/requests/bookmarks_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@
expect(response).to have_http_status(:ok)
end

context "with the bookmarked item's creator" do
let(:creator) { create(:user, person: create(:person, first_name: "Cara", last_name: "Creator")) }

before { workshop.update_columns(created_by_id: creator.id) }

it "links the creator's profile when the viewer can open it" do
create(:affiliation, person: creator.person)

get personal_bookmarks_path, headers: { "Turbo-Frame" => "personal_bookmarks_results" }

expect(response.body).to include(person_path(creator.person))
end

it "shows the creator's name without a link when the viewer can't open the profile" do
get personal_bookmarks_path, headers: { "Turbo-Frame" => "personal_bookmarks_results" }

expect(response.body).not_to include(person_path(creator.person))
expect(response.body).to include("Cara")
end
end

it "can filter personal bookmarks by keyword via turbo frame" do
get personal_bookmarks_path, params: { keyword: workshop.title },
headers: { "Turbo-Frame" => "personal_bookmarks_results" }
Expand Down
40 changes: 40 additions & 0 deletions spec/views/organizations/show.html.erb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

before do
assign(:organization, organization)
sign_in user
allow(view).to receive(:current_user).and_return(user)
render
end
Expand Down Expand Up @@ -40,4 +41,43 @@
expect(rendered).to match(/Facilitator.*Director/)
end
end

context "with people a viewer can't open" do
let(:facilitator) { create(:person, first_name: "Fay", last_name: "Active") }
let(:director) { create(:person, first_name: "Dee", last_name: "Director") }
let(:lapsed) { create(:person, first_name: "Lee", last_name: "Lapsed") }
let(:hidden) { create(:person, first_name: "Hal", last_name: "Hidden", profile_is_searchable: false) }

before do
create(:affiliation, person: facilitator, organization: organization)
create(:affiliation, person: director, organization: organization, title: "Director")
create(:affiliation, person: lapsed, organization: organization, end_date: 1.month.ago)
create(:affiliation, person: hidden, organization: organization)
assign(:organization, organization.reload)
render
end

it "lists only active facilitators whose profile the viewer can open" do
expect(rendered).not_to include("profile-hidden-lock")
expect(rendered).to include(person_path(facilitator))
expect(rendered).not_to include(person_path(director))
expect(rendered).not_to include(person_path(lapsed))
expect(rendered).not_to include(person_path(hidden))
end

context "when the viewer is an admin" do
let(:user) { admin }

it "lists every active facilitator, but not other titles" do
expect(rendered).to include(person_path(facilitator))
expect(rendered).to include(person_path(hidden))
expect(rendered).not_to include(person_path(director))
expect(rendered).not_to include(person_path(lapsed))
end

it "locks only the facilitators others can't open" do
expect(rendered.scan('data-testid="profile-hidden-lock"').count).to eq(1)
end
end
end
end
Loading