diff --git a/app/models/admin_menu.rb b/app/models/admin_menu.rb index 72a770fb7..032a79b93 100644 --- a/app/models/admin_menu.rb +++ b/app/models/admin_menu.rb @@ -4,8 +4,10 @@ class AdminMenu # On second level navigation with more children, we reference the default tabs controller. i.e look at developer_tools # rubocop:disable Metrics/BlockLength ITEMS = Menu.define do - scope :members, "group-2-line", [ - item(name: "members", controller: "users", parent: "users"), + scope :member_manager, "group-2-line", [ + item(name: "members", controller: "users"), + item(name: "invited members", controller: "invitations"), + item(name: "gdpr actions", controller: "gdpr_delete_requests"), ] scope :content_manager, "dashboard-line", [ diff --git a/app/views/admin/gdpr_delete_requests/index.html.erb b/app/views/admin/gdpr_delete_requests/index.html.erb index ee4ff5a3d..2e2bb6970 100644 --- a/app/views/admin/gdpr_delete_requests/index.html.erb +++ b/app/views/admin/gdpr_delete_requests/index.html.erb @@ -2,7 +2,6 @@

Members (GDPR Delete Requests)

- <%= render "admin/users/index/tabs" %>
<%= paginate @gdpr_delete_requests, theme: "admin", scope: @gdpr_delete_requests, label: "Paginate GDPR delete requests", context: "top" %> diff --git a/app/views/admin/invitations/index.html.erb b/app/views/admin/invitations/index.html.erb index 4d6aeafc4..e31955f82 100644 --- a/app/views/admin/invitations/index.html.erb +++ b/app/views/admin/invitations/index.html.erb @@ -3,7 +3,6 @@

Members (Invitations)

- <%= render "admin/users/index/tabs" %>
diff --git a/app/views/admin/shared/_nested_sidebar.html.erb b/app/views/admin/shared/_nested_sidebar.html.erb index 3473e1d15..e06b78e3a 100644 --- a/app/views/admin/shared/_nested_sidebar.html.erb +++ b/app/views/admin/shared/_nested_sidebar.html.erb @@ -22,13 +22,18 @@ <% if item.visible %>
  • style="--bg: transparent" aria-current="page" <% end %>> <%= item.name.to_s.titleize %> + <% if item.controller.to_s == "gdpr_delete_requests" %> + <% if GDPRDeleteRequest.any? %> +  <%= GDPRDeleteRequest.count %> + <% end %> + <% end %>
  • <% end %> diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index ac720d5ff..97c975dd5 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -3,7 +3,6 @@

    Members

    - <%= render "admin/users/index/tabs" %>
    <%= render "admin/users/index/controls" %>
    diff --git a/app/views/admin/users/index/_tabs.html.erb b/app/views/admin/users/index/_tabs.html.erb deleted file mode 100644 index b072d65e9..000000000 --- a/app/views/admin/users/index/_tabs.html.erb +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/config/routes/admin.rb b/config/routes/admin.rb index b2abd37f3..aa9de7c1b 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -22,12 +22,6 @@ namespace :admin do mount PgHero::Engine, at: "pghero" end - resources :invitations, only: %i[index new create destroy] do - member do - post "resend" - end - end - resources :organization_memberships, only: %i[update destroy create] resources :permissions, only: %i[index] resources :reactions, only: %i[update] @@ -44,26 +38,34 @@ namespace :admin do resources :user_experiences, only: [:create] end - resources :gdpr_delete_requests, only: %i[index destroy] + scope :member_manager do + resources :users, only: %i[index show update destroy] do + resources :email_messages, only: :show + collection do + get "export" + end - resources :users, only: %i[index show update destroy] do - resources :email_messages, only: :show - collection do - get "export" + member do + post "banish" + post "export_data" + post "full_delete" + patch "user_status" + post "merge" + delete "remove_identity" + post "send_email" + post "verify_email_ownership" + patch "unlock_access" + post "unpublish_all_articles" + end end - member do - post "banish" - post "export_data" - post "full_delete" - patch "user_status" - post "merge" - delete "remove_identity" - post "send_email" - post "verify_email_ownership" - patch "unlock_access" - post "unpublish_all_articles" + resources :invitations, only: %i[index new create destroy] do + member do + post "resend" + end end + + resources :gdpr_delete_requests, only: %i[index destroy] end scope :content_manager do diff --git a/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js b/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js index 8f8f3efca..104f8b483 100644 --- a/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/gdprDeleteRequests.spec.js @@ -3,7 +3,7 @@ describe('GDPR Delete Requests', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/gdpr_delete_requests'); + cy.loginAndVisit(user, '/admin/member_manager/gdpr_delete_requests'); }); }); diff --git a/cypress/integration/seededFlows/adminFlows/users/invitedUsers.spec.js b/cypress/integration/seededFlows/adminFlows/users/invitedUsers.spec.js index 14e362e18..9ee310312 100644 --- a/cypress/integration/seededFlows/adminFlows/users/invitedUsers.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/invitedUsers.spec.js @@ -10,7 +10,11 @@ describe('Invited users', () => { email: 'test@test.com', }), ) - .then(() => cy.visitAndWaitForUserSideEffects('/admin/invitations')); + .then(() => + cy.visitAndWaitForUserSideEffects( + '/admin/member_manager/invitations', + ), + ); }); }); diff --git a/cypress/integration/seededFlows/adminFlows/users/manageCredits.spec.js b/cypress/integration/seededFlows/adminFlows/users/manageCredits.spec.js index 1623a1bbd..e32ee1e28 100644 --- a/cypress/integration/seededFlows/adminFlows/users/manageCredits.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/manageCredits.spec.js @@ -14,7 +14,7 @@ describe('Manage User Credits', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/users'); + cy.loginAndVisit(user, '/admin/member_manager/users'); cy.findAllByRole('link', { name: 'Credits User' }).first().click(); }); }); diff --git a/cypress/integration/seededFlows/adminFlows/users/manageOrganizations.spec.js b/cypress/integration/seededFlows/adminFlows/users/manageOrganizations.spec.js index e428c8ca4..42a124434 100644 --- a/cypress/integration/seededFlows/adminFlows/users/manageOrganizations.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/manageOrganizations.spec.js @@ -19,7 +19,7 @@ describe('Manage User Organziations', () => { }); it(`should add a user to an organization`, () => { - cy.visit('/admin/users/3'); + cy.visit('/admin/member_manager/users/3'); cy.findByText('Not part of any organization yet.').should('be.visible'); @@ -45,7 +45,7 @@ describe('Manage User Organziations', () => { }); it('should add a user to multiple organizations', () => { - cy.visit('/admin/users/3'); + cy.visit('/admin/member_manager/users/3'); cy.findByText('Not part of any organization yet.').should('be.visible'); @@ -89,7 +89,7 @@ describe('Manage User Organziations', () => { }); it(`should edit a user's membership to an organization`, () => { - cy.visit('/admin/users/2'); + cy.visit('/admin/member_manager/users/2'); cy.findAllByRole('link', { name: 'Awesome Org' }).first().focus(); cy.findByRole('button', { @@ -108,7 +108,7 @@ describe('Manage User Organziations', () => { }); it(`should add a user to another organization`, () => { - cy.visit('/admin/users/2'); + cy.visit('/admin/member_manager/users/2'); openOrgModal('Add organization').within(() => { cy.findByRole('spinbutton', { name: 'Organization ID' }).type(1); @@ -129,7 +129,7 @@ describe('Manage User Organziations', () => { }); it(`should revoke a user's membership to an organization`, () => { - cy.visit('/admin/users/2'); + cy.visit('/admin/member_manager/users/2'); cy.findAllByRole('link', { name: 'Awesome Org' }).first().focus(); cy.findByRole('button', { diff --git a/cypress/integration/seededFlows/adminFlows/users/manageRoles.spec.js b/cypress/integration/seededFlows/adminFlows/users/manageRoles.spec.js index 4d5706f5c..0e8725288 100644 --- a/cypress/integration/seededFlows/adminFlows/users/manageRoles.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/manageRoles.spec.js @@ -24,7 +24,7 @@ describe('Manage User Roles', () => { describe('Changing Roles', () => { beforeEach(() => { - cy.visit('/admin/users/2'); + cy.visit('/admin/member_manager/users/2'); }); it('should change a role', () => { @@ -88,7 +88,7 @@ describe('Manage User Roles', () => { describe('Adding Roles', () => { beforeEach(() => { - cy.visit('/admin/users/3'); + cy.visit('/admin/member_manager/users/3'); }); it('should not add a role if a reason is missing.', () => { diff --git a/cypress/integration/seededFlows/adminFlows/users/manageUserOptions.spec.js b/cypress/integration/seededFlows/adminFlows/users/manageUserOptions.spec.js index aa95a727c..1d76343b8 100644 --- a/cypress/integration/seededFlows/adminFlows/users/manageUserOptions.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/manageUserOptions.spec.js @@ -19,7 +19,7 @@ describe('Manage User Options', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/users/2'); + cy.loginAndVisit(user, '/admin/member_manager/users/2'); }); }); diff --git a/cypress/integration/seededFlows/adminFlows/users/navigateTabs.spec.js b/cypress/integration/seededFlows/adminFlows/users/navigateTabs.spec.js index dbe3ab6fd..499ff0de1 100644 --- a/cypress/integration/seededFlows/adminFlows/users/navigateTabs.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/navigateTabs.spec.js @@ -4,7 +4,7 @@ describe('Navigate User Tabs', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/users/2'); + cy.loginAndVisit(user, '/admin/member_manager/users/2'); }); }); @@ -19,25 +19,25 @@ describe('Navigate User Tabs', () => { it(`navigates to the "Notes" tab`, () => { cy.findByRole('link', { name: /Notes/i }).should('exist'); cy.findByRole('link', { name: /Notes/i }).click(); - cy.url().should('contain', '/admin/users/2?tab=notes'); + cy.url().should('contain', '/admin/member_manager/users/2?tab=notes'); }); it(`navigates to the "Emails" tab`, () => { cy.findByRole('link', { name: /Emails/i }).should('exist'); cy.findByRole('link', { name: /Emails/i }).click(); - cy.url().should('contain', '/admin/users/2?tab=emails'); + cy.url().should('contain', '/admin/member_manager/users/2?tab=emails'); }); it(`navigates to the "Reports" tab`, () => { cy.findByRole('link', { name: /Reports/i }).should('exist'); cy.findByRole('link', { name: /Reports/i }).click(); - cy.url().should('contain', '/admin/users/2?tab=reports'); + cy.url().should('contain', '/admin/member_manager/users/2?tab=reports'); }); it(`navigates to the "Flags" tab`, () => { cy.findByRole('link', { name: /Flags/i }).should('exist'); cy.findByRole('link', { name: /Flags/i }).click(); - cy.url().should('contain', '/admin/users/2?tab=flags'); + cy.url().should('contain', '/admin/member_manager/users/2?tab=flags'); }); }); }); diff --git a/cypress/integration/seededFlows/adminFlows/users/userIndexView.spec.js b/cypress/integration/seededFlows/adminFlows/users/userIndexView.spec.js index bf33be71d..c668d6726 100644 --- a/cypress/integration/seededFlows/adminFlows/users/userIndexView.spec.js +++ b/cypress/integration/seededFlows/adminFlows/users/userIndexView.spec.js @@ -4,7 +4,7 @@ describe('User index view', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/users'); + cy.loginAndVisit(user, '/admin/member_manager/users'); }); cy.viewport('iphone-x'); }); @@ -139,7 +139,7 @@ describe('User index view', () => { it(`Clicks through to the Member Detail View`, () => { cy.findAllByRole('link', { name: 'Admin McAdmin' }).first().click(); - cy.url().should('contain', '/admin/users/1'); + cy.url().should('contain', '/admin/member_manager/users/1'); }); }); @@ -177,7 +177,7 @@ describe('User index view', () => { cy.testSetup(); cy.fixture('users/adminUser.json').as('user'); cy.get('@user').then((user) => { - cy.loginAndVisit(user, '/admin/users'); + cy.loginAndVisit(user, '/admin/member_manager/users'); }); cy.viewport('macbook-16'); }); @@ -229,7 +229,7 @@ describe('User index view', () => { it(`Clicks through to the Member Detail View`, () => { cy.findAllByRole('link', { name: 'Admin McAdmin' }).first().click(); - cy.url().should('contain', '/admin/users/1'); + cy.url().should('contain', '/admin/member_manager/users/1'); }); }); @@ -274,7 +274,7 @@ describe('User index view', () => { ).should('be.visible'); cy.findByRole('link', { name: 'Download', - href: '/admin/users/export.csv', + href: '/admin/member_manager/users/export.csv', }).should('exist'); }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 07c875a1f..31f54ca4e 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -395,7 +395,7 @@ Cypress.Commands.add( Cypress.Commands.add('inviteUser', ({ name, email }) => { return cy.request( 'POST', - '/admin/invitations', + '/admin/member_manager/invitations', `utf8=%E2%9C%93&user%5Bemail%5D=${email}&user%5Bname%5D=${name}&commit=Invite+User`, ); }); diff --git a/spec/requests/admin/gdpr_delete_requests_spec.rb b/spec/requests/admin/gdpr_delete_requests_spec.rb index ac1cefe03..d700fc2bc 100644 --- a/spec/requests/admin/gdpr_delete_requests_spec.rb +++ b/spec/requests/admin/gdpr_delete_requests_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "/admin/gdpr_delete_requests", type: :request do +RSpec.describe "/admin/member_manager/gdpr_delete_requests", type: :request do let(:admin) { create(:user, :super_admin) } before do @@ -22,7 +22,7 @@ RSpec.describe "/admin/gdpr_delete_requests", type: :request do it "displays the number of existing requests" do get admin_gdpr_delete_requests_path - expect(response.body).to include("1") + expect(response.body).to include("1") end it "destroys the gdpr delete request on confirmation" do @@ -41,7 +41,7 @@ RSpec.describe "/admin/gdpr_delete_requests", type: :request do context "without gdpr request" do it "doesn't display the number of existing requests" do get admin_gdpr_delete_requests_path - expect(response.body).not_to include("0") + expect(response.body).not_to include("0") end end end diff --git a/spec/requests/admin/invitations_spec.rb b/spec/requests/admin/invitations_spec.rb index b1308ff82..8aaed6972 100644 --- a/spec/requests/admin/invitations_spec.rb +++ b/spec/requests/admin/invitations_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "/admin/invitations", type: :request do +RSpec.describe "/admin/member_manager/invitations", type: :request do let(:user) { create(:user) } let(:admin) { create(:user, :super_admin) } @@ -9,7 +9,7 @@ RSpec.describe "/admin/invitations", type: :request do allow(ForemInstance).to receive(:smtp_enabled?).and_return(true) end - describe "GET /admin/invitations" do + describe "GET /admin/member_manager/invitations" do it "renders to appropriate page" do user.update_column(:registered, false) get admin_invitations_path @@ -17,14 +17,14 @@ RSpec.describe "/admin/invitations", type: :request do end end - describe "GET /admin/invitations/new" do + describe "GET /admin/member_manager/invitations/new" do it "renders to appropriate page" do get new_admin_invitation_path expect(response.body).to include("Email") end end - describe "POST /admin/invitations" do + describe "POST /admin/member_manager/invitations" do it "creates new invitation" do post admin_invitations_path, params: { user: { email: "hey#{rand(1000)}@email.co" } } @@ -50,7 +50,7 @@ RSpec.describe "/admin/invitations", type: :request do end end - describe "POST /admin/invitations/:id/resend" do + describe "POST /admin/member_manager/invitations/:id/resend" do let!(:invitation) { create(:user, registered: false) } it "enqueues an invitation email to be resent" do @@ -61,7 +61,7 @@ RSpec.describe "/admin/invitations", type: :request do end end - describe "DELETE /admin/invitations" do + describe "DELETE /admin/member_manager/invitations" do let!(:invitation) { create(:user, registered: false) } before do diff --git a/spec/requests/admin/users_spec.rb b/spec/requests/admin/users_spec.rb index fb2f62ed3..62ab248bc 100644 --- a/spec/requests/admin/users_spec.rb +++ b/spec/requests/admin/users_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "/admin/users", type: :request do +RSpec.describe "/admin/member_manager/users", type: :request do let!(:user) do omniauth_mock_github_payload create(:user, :with_identity, identities: ["github"]) @@ -11,7 +11,7 @@ RSpec.describe "/admin/users", type: :request do sign_in(admin) end - describe "GET /admin/users" do + describe "GET /admin/member_manager/users" do it "renders to appropriate page" do get admin_users_path expect(response.body).to include(user.username) @@ -32,7 +32,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "GET /admin/users/:id" do + describe "GET /admin/member_manager/users/:id" do it "renders to appropriate page" do get admin_user_path(user) @@ -60,17 +60,17 @@ RSpec.describe "/admin/users", type: :request do end it "displays a user's current roles in the 'Emails' tab" do - get "/admin/users/#{user.id}?tab=emails" + get "#{admin_user_path(user.id)}?tab=emails" expect(response.body).to include("Previous emails") end it "displays a user's current flags in the 'Flags' tab" do - get "/admin/users/#{user.id}?tab=flags" + get "#{admin_user_path(user.id)}?tab=flags" expect(response.body).to include("Flags received") end it "displays a message when there are no related vomit reactions for a user" do - get "/admin/users/#{user.id}?tab=flags" + get "#{admin_user_path(user.id)}?tab=flags" expect(response.body).to include("No flags received against") end @@ -81,12 +81,12 @@ RSpec.describe "/admin/users", type: :request do end it "displays a user's current reports in the 'Reports' tab" do - get "/admin/users/#{user.id}?tab=reports" + get "#{admin_user_path(user.id)}?tab=reports" expect(response.body).to include("Reports submitted by") end it "displays a message when there are no current reports for a user" do - get "/admin/users/#{user.id}?tab=reports" + get "#{admin_user_path(user.id)}?tab=reports" expect(response.body).to include("No comment or post has been reported yet.") end @@ -97,7 +97,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "POST /admin/users/:id/banish" do + describe "POST /admin/member_manager/users/:id/banish" do it "bans user for spam" do allow(Moderator::BanishUserWorker).to receive(:perform_async) post banish_admin_user_path(user.id) @@ -106,7 +106,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "POST /admin/users/:id/send_email" do + describe "POST /admin/member_manager/users/:id/send_email" do let(:params) do { email_body: "Body", @@ -188,7 +188,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "POST /admin/users/:id/verify_email_ownership" do + describe "POST /admin/member_manager/users/:id/verify_email_ownership" do let(:mailer) { double } let(:message_delivery) { double } @@ -297,7 +297,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "POST /admin/users/:id/unpublish_all_articles" do + describe "POST /admin/member_manager/users/:id/unpublish_all_articles" do let(:user) { create(:user) } it "unpublishes all articles" do @@ -307,7 +307,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "DELETE /admin/users/:id/remove_identity" do + describe "DELETE /admin/member_manager/users/:id/remove_identity" do let(:provider) { Authentication::Providers.available.first } let(:user) do omniauth_mock_providers_payload @@ -367,7 +367,7 @@ RSpec.describe "/admin/users", type: :request do end end - describe "POST /admin/users/:id/export_data" do + describe "POST /admin/member_manager/users/:id/export_data" do it "redirects properly to the user edit page" do sign_in admin post export_data_admin_user_path(user), params: { send_to_admin: "true" }