Update the Sidebar to include the Member Manager (#17538)

* feat: update all the paths as a first order of business

* feat: update all the folders to no longer appear under users

* feat: update specs

* feat: update specs

* feat: remove User namespace

* feat: remove route namespace

* feat: path change

* feat: move the menu items to the sidebar

* feat: add a badge

* feat: remove tabs partial

* xs font

* feat: update describe blocks

* feat: update cypress tests

* chore: oops member_manager instead of member_management

* feat: c-indicator

* feat: update the tests

* feat: update the gdpr test

* feat: add the invitations

* chore: align

* feat: update the GDPR constant

* feat: rename to GDPR actions

* feat: add member manager route
This commit is contained in:
Ridhwana 2022-05-05 12:46:47 +02:00 committed by GitHub
parent 50d2359049
commit 6f880ee5b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 83 additions and 92 deletions

View file

@ -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", [

View file

@ -2,7 +2,6 @@
<div id="gdpr-delete-requests-content" class="crayons-card overflow-admin-main-layout-padding p-4 m:p-0">
<header class="flex flex-col l:flex-row justify-content-between l:items-center p-0 m:p-7 pb-4">
<h1 class="crayons-title ml-3 l:ml-0">Members<span class="screen-reader-only"> (GDPR Delete Requests)</span></h1>
<%= render "admin/users/index/tabs" %>
</header>
<div class="flex justify-end px-2 m:px-7">
<%= paginate @gdpr_delete_requests, theme: "admin", scope: @gdpr_delete_requests, label: "Paginate GDPR delete requests", context: "top" %>

View file

@ -3,7 +3,6 @@
<header class="flex flex-col p-4 xl:p-7 pb-4">
<div class="flex flex-col l:flex-row justify-content-between l:items-center">
<h1 class="crayons-title ml-3 xl:ml-0">Members<span class="screen-reader-only"> (Invitations)</span></h1>
<%= render "admin/users/index/tabs" %>
</div>
<!-- Small screen layout start -->

View file

@ -22,13 +22,18 @@
<% if item.visible %>
<li>
<a
class="c-link c-link--block"
class="c-link c-link--block inline-block"
href="<%= admin_path %>/<%= group_name %>/<%= item.controller %>"
<% if deduced_controller(request) == item.controller.to_s %>
style="--bg: transparent"
aria-current="page"
<% end %>>
<%= item.name.to_s.titleize %>
<% if item.controller.to_s == "gdpr_delete_requests" %>
<% if GDPRDeleteRequest.any? %>
&nbsp;<span class="c-indicator c-indicator--info fs-xs"><%= GDPRDeleteRequest.count %></span>
<% end %>
<% end %>
</a>
</li>
<% end %>

View file

@ -3,7 +3,6 @@
<header class="xl:p-7 pb-4">
<div class="flex flex-col l:flex-row justify-content-between l:items-center">
<h1 class="crayons-title ml-2 m:ml-0">Members</h1>
<%= render "admin/users/index/tabs" %>
</div>
<%= render "admin/users/index/controls" %>
</header>

View file

@ -1,19 +0,0 @@
<nav aria-label="Members" class="flex justify-between gap-4">
<ul class="crayons-navigation crayons-navigation--horizontal">
<li>
<%= link_to "Members", admin_users_path, class: "crayons-navigation__item #{'crayons-navigation__item--current' if params[:controller] == 'admin/users'}", "aria-current": ("page" if params[:controller] == "admin/users") %>
</li>
<li>
<%= link_to "Invited Members", admin_invitations_path, class: "crayons-navigation__item #{'crayons-navigation__item--current' if params[:controller] == 'admin/invitations'}", "aria-current": ("page" if params[:controller] == "admin/invitations") %>
</li>
<li>
<%= link_to admin_gdpr_delete_requests_path, class: "crayons-navigation__item #{'crayons-navigation__item--current' if params[:controller] == 'admin/gdpr_delete_requests'}", "aria-current": ("page" if params[:controller] == "admin/gdpr_delete_requests") do %>
GDPR Delete Requests
<% if GDPRDeleteRequest.any? %>
&nbsp;<span class="c-indicator c-indicator--danger"><%= GDPRDeleteRequest.count %></span>
<% end %>
<% end %>
</li>
</ul>
</nav>

View file

@ -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

View file

@ -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');
});
});

View file

@ -10,7 +10,11 @@ describe('Invited users', () => {
email: 'test@test.com',
}),
)
.then(() => cy.visitAndWaitForUserSideEffects('/admin/invitations'));
.then(() =>
cy.visitAndWaitForUserSideEffects(
'/admin/member_manager/invitations',
),
);
});
});

View file

@ -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();
});
});

View file

@ -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', {

View file

@ -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.', () => {

View file

@ -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');
});
});

View file

@ -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');
});
});
});

View file

@ -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');
});
});

View file

@ -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`,
);
});

View file

@ -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("<span class=\"c-indicator c-indicator--danger\">1</span>")
expect(response.body).to include("<span class=\"c-indicator c-indicator--info fs-xs\">1</span>")
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("<span class=\"c-indicator c-indicator--danger\">0</span>")
expect(response.body).not_to include("<span class=\"c-indicator c-indicator--info fs-xs\">0</span>")
end
end
end

View file

@ -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

View file

@ -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" }