Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- Bump rexml from 3.4.1 to 3.4.2 [#3562](https://github.com/DMPRoadmap/roadmap/pull/3562)
- Bump tmp from 0.2.3 to 0.2.4 [#3548](https://github.com/DMPRoadmap/roadmap/pull/3548)
- Update Gems and Address New Rubocop Offences [#3572](https://github.com/DMPRoadmap/roadmap/pull/3572)
- Fix for a discrepancy in Org Admin view of the Organisation Plans and the related CSV Download. [#3561] (https://github.com/DMPRoadmap/roadmap/issues/3561)

## v5.0.1
- Updated seeds.rb file for identifier_schemes to include context value and removed logo_url and idenitifier_prefix for Shibboleth (as it was causing issues with SSO). [#3525](https://github.com/DMPRoadmap/roadmap/pull/3525)
Expand Down
10 changes: 8 additions & 2 deletions app/controllers/paginable/plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ def org_admin
# check if current user if super_admin
@super_admin = current_user.can_super_admin?
@clicked_through = params[:click_through].present?
plans = @super_admin ? Plan.all : current_user.org.org_admin_plans
plans = plans.joins(:template, roles: [user: :org]).where(Role.creator_condition)

plans = if @super_admin
Plan.all.joins(:template, roles: [user: :org])
.where(Role.creator_condition)
else
current_user.org.org_admin_plans
.joins(:template, roles: [user: :org])
end

paginable_renderise(
partial: 'org_admin',
Expand Down
2 changes: 2 additions & 0 deletions app/models/org.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,11 +293,13 @@ def org_admin_plans
if Rails.configuration.x.plans.org_admins_read_all
Plan.includes(:template, :phases, :roles, :users).where(id: combined_plan_ids)
.where(roles: { active: true })
.where(Role.creator_condition)
else
Plan.includes(:template, :phases, :roles, :users).where(id: combined_plan_ids)
.where.not(visibility: Plan.visibilities[:privately_visible])
.where.not(visibility: Plan.visibilities[:is_test])
.where(roles: { active: true })
.where(Role.creator_condition)
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if a refactor like the following would be a nice improvement:

def org_admin_plans
  scope = Plan.includes(:template, :phases, :roles, :users)
              .where(id: (native_plan_ids + affiliated_plan_ids).uniq)
              .where(roles: { active: true })
              .where(Role.creator_condition)

  unless Rails.configuration.x.plans.org_admins_read_all
    scope = scope.where.not(visibility: Plan.visibilities[:privately_visible])
                 .where.not(visibility: Plan.visibilities[:is_test])
  end

  scope
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for refactor advice. Nice and neat. Will add commit with change.

end
end
# rubocop:enable Metrics/AbcSize
Expand Down
15 changes: 15 additions & 0 deletions spec/factories/plans.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,26 @@
obj.roles << create(:role, :creator, user: create(:user, org: create(:org)))
end
end
trait :administrator do
after(:create) do |obj|
obj.roles << create(:role, :administrator, user: create(:user, org: create(:org)))
end
end
trait :editor do
after(:create) do |obj|
obj.roles << create(:role, :editor, user: create(:user, org: create(:org)))
end
end
trait :commenter do
after(:create) do |obj|
obj.roles << create(:role, :commenter, user: create(:user, org: create(:org)))
end
end
trait :reviewer do
after(:create) do |obj|
obj.roles << create(:role, :reviewer, user: create(:user, org: create(:org)))
end
end
trait :organisationally_visible do
after(:create) do |plan|
plan.update(visibility: Plan.visibilities[:organisationally_visible])
Expand Down
101 changes: 73 additions & 28 deletions spec/models/org_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -344,79 +344,124 @@

describe '#org_admin_plans' do
Rails.configuration.x.plans.org_admins_read_all = true
let!(:org) { create(:org) }
let!(:plan) { create(:plan, org: org, visibility: 'publicly_visible') }
let!(:user) { create(:user, org: org) }
# Two Orgs
let!(:org1) { create(:org) }
let!(:org2) { create(:org) }

# Plans for org1
let!(:org1plan1) { create(:plan, :creator, :organisationally_visible, org: org1) }
let!(:org1plan2) { create(:plan, :creator, :privately_visible, org: org1) }
let!(:org1plan3) { create(:plan, :creator, :publicly_visible, org: org1) }
let!(:org1plan4) { create(:plan, :creator, :organisationally_visible, org: org1) }

subject { org.org_admin_plans }
# Plans for org2
let!(:org2plan1) { create(:plan, :creator, :organisationally_visible, org: org2) }

subject { org1.org_admin_plans }

context 'when user belongs to Org and plan owner with role :creator' do
before do
create(:role, :creator, user: user, plan: plan)
plan.add_user!(user.id, :creator)
Rails.configuration.x.plans.org_admins_read_all = true
end
it { is_expected.to include(org1plan1, org1plan2, org1plan3, org1plan4) }
it { is_expected.not_to include(org2plan1) }
end

it { is_expected.to include(plan) }
context 'when user belongs to Org and a plan removed by creator assuming there are no coowners' do
before do
Rails.configuration.x.plans.org_admins_read_all = true
org1plan4.roles.map { |r| r.update(active: false) if r.user_id == org1plan4.owner.id }
end

it { is_expected.to include(org1plan1, org1plan2, org1plan3) }
it { is_expected.not_to include(org1plan4) }
end

context 'when user belongs to Org and plan user with role :administrator' do
context 'when user belongs to Org and a plan removed by creator, but cowner still active.' do
before do
plan.add_user!(user.id, :administrator)
Rails.configuration.x.plans.org_admins_read_all = true
coowner = create(:user, org: org1)
org1plan4.add_user!(coowner.id, :coowner)
Copy link
Contributor

@aaronskiba aaronskiba Dec 3, 2025

Choose a reason for hiding this comment

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

You're trying to add a :coowner here, but it looks like a :commenter is being added instead:

  # Creates a role for the specified user (will update the user's
  # existing role if it already exists)
  #
  # Expects a User.id and access_type from the following list:
  #  :creator, :administrator, :editor, :commenter
  #
  # Returns Boolean
  def add_user!(user_id, access_type = :commenter)
    user = User.where(id: user_id).first
    if user.present?
      role = Role.find_or_initialize_by(user_id: user_id, plan_id: id)

      # Access is cumulative, so set the appropriate flags
      # (e.g. an administrator can also edit and comment)
      case access_type
      when :creator
        role.creator = true
        role.administrator = true
        role.editor = true
      when :administrator
        role.administrator = true
        role.editor = true
      when :editor
        role.editor = true
      end
      role.commenter = true
      role.save
    else
      false
    end
  end
   386:         org1plan4.roles.map { |r| r.update(active: false) if r.user_id == owner_id }
   387:         byebug
=> 388:       end
   389: 
   390:       it { is_expected.to include(org1plan1, org1plan2, org1plan3) }
   391:       it { is_expected.not_to include(org1plan4) }
   392:     end
(byebug) roles = org1plan4.roles
#<ActiveRecord::Associations::CollectionProxy [#<Role id: 4, user_id: 4, plan_id: 7, created_at: "2025-12-03 17:37:57.852288000 +0000", updated_at: "2025-12-03 17:37:57.855932000 +0000", access: 15, active: false>, #<Role id: 6, user_id: 6, plan_id: 7, created_at: "2025-12-03 17:37:57.987364000 +0000", updated_at: "2025-12-03 17:37:57.987364000 +0000", access: 8, active: true>]>
(byebug) roles.last.creator?
false
(byebug) roles.last.administrator?
false
(byebug) roles.last.editor?
false
(byebug) roles.last.commenter?
true
(byebug) roles.last.reviewer?
false
(byebug) 

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad. Only allowed access_types ":creator, :administrator, :editor, :commenter". Will update code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated tests to only add access_type :administrator for a coowner test.

owner_id = org1plan4.owner.id
org1plan4.roles.map { |r| r.update(active: false) if r.user_id == owner_id }
end

it { is_expected.to include(org1plan1, org1plan2, org1plan3) }
it { is_expected.not_to include(org1plan4) }
end

context 'when user belongs to Org, plan user with role :administrator, but plan creator from a different Org' do
before do
Rails.configuration.x.plans.org_admins_read_all = true
# Creator belongs to different org
@plan = create(:plan, :creator, :organisationally_visible, org: create(:org))
@plan.add_user!(create(:user, org: org1).id, :administrator)
end

it {
is_expected.to include(plan)
is_expected.to include(org1plan1, org1plan2, org1plan3, org1plan4, @plan)
}
end

context 'user belongs to Org and plan user with role :editor, but not :creator and :admin' do
before do
plan.add_user!(user.id, :editor)
Rails.configuration.x.plans.org_admins_read_all = true
# Creator and admin belongs to different orgs
@plan = create(:plan, :creator, :organisationally_visible, org: create(:org))
@plan.add_user!(create(:org).id, :administrator)
# Editor belongs to org1
@plan.add_user!(create(:user, org: org1).id, :editor)
end

it { is_expected.to include(plan) }
it { is_expected.not_to include(@plan) }
end

context 'user belongs to Org and plan user with role :commenter, but not :creator and :admin' do
before do
plan.add_user!(user.id, :commenter)
Rails.configuration.x.plans.org_admins_read_all = true
# Creator and admin belongs to different orgs
@plan = create(:plan, :creator, :organisationally_visible, org: create(:org))
@plan.add_user!(create(:org).id, :administrator)
# Commenter belongs to org1
@plan.add_user!(create(:user, org: org1).id, :commentor)
end

it { is_expected.to include(plan) }
it { is_expected.not_to include(@plan) }
end

context 'user belongs to Org and plan user with role :reviewer, but not :creator and :admin' do
before do
plan.add_user!(user.id, :reviewer)
Rails.configuration.x.plans.org_admins_read_all = true
# Creator and admin belongs to different orgs
@plan = create(:plan, :creator, :organisationally_visible, org: create(:org))
@plan.add_user!(create(:org).id, :administrator)
# Reviewer belongs to org1
@plan.add_user!(create(:user, org: org1).id, :reviewer)
end

it { is_expected.to include(plan) }
it { is_expected.not_to include(@plan) }
end

context 'read_all is false, visibility private and user org_admin' do
before do
Rails.configuration.x.plans.org_admins_read_all = false
@perm = build(:perm)
@perm.name = 'grant_permissions'
user.perms << @perm
plan.add_user!(user.id, :reviewer)
plan.privately_visible!
@user = create(:user, :org_admin, org: org1)
@plan = create(:plan, :creator, :privately_visible, org: org1)
@plan.add_user!(@user.id, :reviewer)
end

it { is_expected.not_to include(plan) }
it { is_expected.not_to include(@plan) }
end

context 'read_all is false, visibility public and user org_admin' do
before do
Rails.configuration.x.plans.org_admins_read_all = false
@perm = build(:perm)
@perm.name = 'grant_permissions'
user.perms << @perm
plan.add_user!(user.id, :reviewer)
plan.publicly_visible!
@user = create(:user, :org_admin, org: org1)
@plan = create(:plan, :creator, :publicly_visible, org: org1)
@plan.add_user!(@user.id, :reviewer)
end

it { is_expected.to include(plan) }
it { is_expected.to include(@plan) }
end
end

Expand Down