Add Data (User Details, Status, etc.) to the Member Index View Columns (#17033)
* Adds data to the member column in Member Index View * Adds roles, statuses, and latest activity to Member Index views * Adds the list of orgs a user belongs to in Member Index view * Updates the image URL to link to each users member detail view * Adds comment_suspended to user current_status and makes roles prettier * Fixes bullet warning and encapsulates org logic in method * Properly displays today and yesterday in words * Refactors #current_organizations to use ternary * Updates user.rb Member Index View-related methods * Adjusts the styling of user roles and adds tooltip to roles * Updates Member Index View to be mobile-friendly * Updates userIndexView.spec.js * Updates capitalization within userIndexView.spec.js * Updates Member Index View per mobile designs and feedback * Adds member status logic inline with SVGs * Updates the status column for mobile layout * Adjust current_role tooltips for Member Index View * Adds organization name tooltip to member orgas * Adds organization pics to Member Index View and cleans up helpers * Addresses PR review feedback (Thanks, Suzanne!) * Uses CSS in place of SVGs for member statuses * Adds E2E test for and removes link to org page from Member Index View * Addresses PR feedback comments by refactoring Member Index View code * Removes leftover code in user.rb * Update method that tooltips rely on * Spells regular correctly : * Adds a spec that tests Admin::UsersHelper#format_last_activity_timestamp * Adjusts #cascading_high_level_roles logic and roles * Adds a spec for #cascading_high_level_roles * Adds a #last_activity spec to the User model * Adjusts code a bit per PR feedback. Thanks, Ridhwana! :) * Updates specs for Member Index View helpers * Adds Timecop.freeze to help with timestamp failure in user_spec.rb
This commit is contained in:
parent
8c2b1276a4
commit
ccf33c50a0
6 changed files with 195 additions and 20 deletions
25
app/helpers/admin/users_helper.rb
Normal file
25
app/helpers/admin/users_helper.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
module Admin
|
||||
module UsersHelper
|
||||
def format_last_activity_timestamp(timestamp)
|
||||
return if timestamp.blank?
|
||||
|
||||
if timestamp.today?
|
||||
"Today, #{timestamp.strftime('%d %b')}"
|
||||
elsif timestamp.yesterday?
|
||||
"Yesterday, #{timestamp.strftime('%d %b')}"
|
||||
else
|
||||
timestamp.strftime("%d %b, %Y")
|
||||
end
|
||||
end
|
||||
|
||||
def cascading_high_level_roles(user)
|
||||
if user.super_admin?
|
||||
"Super Admin"
|
||||
elsif user.admin?
|
||||
"Admin"
|
||||
elsif user.single_resource_admin_for?(:any)
|
||||
"Resource Admin"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -554,6 +554,13 @@ class User < ApplicationRecord
|
|||
Reaction.for_user(self)
|
||||
end
|
||||
|
||||
def last_activity
|
||||
return unless registered == true
|
||||
|
||||
[registered_at, last_comment_at, last_article_at, latest_article_updated_at, last_reacted_at, profile_updated_at,
|
||||
last_moderation_notification, last_notification_activity].compact.max
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Send emails asynchronously
|
||||
|
|
|
|||
|
|
@ -7,34 +7,59 @@
|
|||
|
||||
<!-- Small screen data view start -->
|
||||
<div class="block m:hidden fs-s">
|
||||
<h2 class="member-data-heading fs-s py-2 -mx-4 px-6 color-base-60">Members</h2>
|
||||
<ul class="list-none mx-2">
|
||||
<% @users.each do |user| %>
|
||||
<h2 class="member-data-heading fs-s py-2 -mx-3 px-3 color-base-60">Members</h2>
|
||||
<ul class="list-none">
|
||||
<% @users.includes([:organization_memberships]).each do |user| %>
|
||||
<li class="py-4">
|
||||
<article>
|
||||
<header class="flex">
|
||||
<img class="radius-full mr-2" src="<%= user.profile_image_url_for(length: 50) %>" width="40" height="40" alt="" />
|
||||
<div class="flex flex-col">
|
||||
<h3 class="fs-base"><%= link_to user.name, admin_user_path(user) %></h3>
|
||||
<span class="color-base-60">@<%= user.username %></span>
|
||||
<a href="<%= admin_user_path(user.id) %>">
|
||||
<img class="radius-full align-middle" src="<%= user.profile_image_url_for(length: 50) %>" width="40" height="40" alt="<%= user.name %>" />
|
||||
</a>
|
||||
<div class="pl-3 flex-1 fs-s"><h3 class="fs-base"><%= link_to user.name, admin_user_path(user), class: "color-base-80" %></h3>
|
||||
<%= link_to "@#{user.username}", admin_user_path(user), class: "color-base-60" %>
|
||||
</div>
|
||||
</header>
|
||||
<div class="flex justify-between py-2">
|
||||
<div>standing info</div>
|
||||
<div>role info</div>
|
||||
<div>
|
||||
<% if user.suspended? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #DC2626"></span>Suspended
|
||||
<% elsif user.warned? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #F59E0B"></span>Warned
|
||||
<% elsif user.comment_suspended? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #DC2626"></span>Comment suspended
|
||||
<% elsif user.trusted? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #059669"></span>Trusted
|
||||
<% else %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #A3A3A3"></span>Good standing
|
||||
<% end %>
|
||||
</div>
|
||||
<div>
|
||||
<span class="c-indicator ml-2 crayons-hover-tooltip" data-tooltip="<%= cascading_high_level_roles(user) %>">
|
||||
<%= cascading_high_level_roles(user) %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between items-end">
|
||||
<dl class="flex gap-2">
|
||||
<div>
|
||||
<dt class="color-base-60 fw-normal">Last activity</dt>
|
||||
<dd>placeholder</dd>
|
||||
<dd><%= format_last_activity_timestamp(user.last_activity) %></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="color-base-60 fw-normal">Joined on</dt>
|
||||
<dd><%= user.registered_at.strftime("%d %b, %Y") %></dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div>Orgs info</div>
|
||||
<div>
|
||||
<% if user.organization_memberships.any? %>
|
||||
<% user.organization_memberships.includes([:organization]).each do |org_membership| %>
|
||||
<span class="crayons-hover-tooltip" data-tooltip="<%= org_membership.organization.name %>">
|
||||
<img src="<%= org_membership.organization.profile_image_url_for(length: 64) %>" width="32" height="32" alt="<%= org_membership.organization.name %>" class="c-link crayons-logo crayons-logo--l">
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
|
|
@ -54,9 +79,51 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody class="crayons-card">
|
||||
<% @users.each do |user| %>
|
||||
<% @users.includes([:organization_memberships]).each do |user| %>
|
||||
<tr>
|
||||
<td><%= link_to user.name, admin_user_path(user) %></td>
|
||||
<td>
|
||||
<div class="flex s:items-start flex-col s:flex-row">
|
||||
<a href="<%= admin_user_path(user.id) %>">
|
||||
<img class="radius-full align-middle" src="<%= user.profile_image_url_for(length: 50) %>" width="40" height="40" alt="<%= user.name %>" />
|
||||
</a>
|
||||
<div class="pl-3 flex-1 fs-s"><h3 class="fs-base"><%= link_to user.name, admin_user_path(user), class: "color-base-80" %></h3>
|
||||
<%= link_to "@#{user.username}", admin_user_path(user), class: "color-base-60" %>
|
||||
<span class="c-indicator ml-2 crayons-hover-tooltip" data-tooltip="<%= cascading_high_level_roles(user) %>">
|
||||
<%= cascading_high_level_roles(user) %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<% if user.suspended? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #DC2626"></span>Suspended
|
||||
<% elsif user.warned? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #F59E0B"></span>Warned
|
||||
<% elsif user.comment_suspended? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #DC2626"></span>Comment suspended
|
||||
<% elsif user.trusted? %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #059669"></span>Trusted
|
||||
<% else %>
|
||||
<span class="c-indicator mr-2 inline-block" style="--bg: #A3A3A3"></span>Good standing
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
<%= format_last_activity_timestamp(user.last_activity) %>
|
||||
<p class="fs-xs color-base-60">
|
||||
<%= user.registered_at.strftime("%d %b, %Y") %>
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<% if user.organization_memberships.any? %>
|
||||
<% user.organization_memberships.includes([:organization]).each do |org_membership| %>
|
||||
<span class="crayons-hover-tooltip" data-tooltip="<%= org_membership.organization.name %>">
|
||||
<img src="<%= org_membership.organization.profile_image_url_for(length: 64) %>" width="32" height="32" alt="<%= org_membership.organization.name %>" class="c-link crayons-logo crayons-logo--l">
|
||||
</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
-
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ describe('User index view', () => {
|
|||
|
||||
cy.findByRole('button', { name: 'Search' }).click();
|
||||
|
||||
// Coreect search result should appear
|
||||
// Correct search result should appear
|
||||
cy.findByRole('heading', { name: 'Admin McAdmin' }).should('exist');
|
||||
});
|
||||
|
||||
|
|
@ -41,11 +41,9 @@ describe('User index view', () => {
|
|||
cy.findByRole('combobox', { name: 'User role' }).select('super_admin');
|
||||
cy.findByRole('button', { name: 'Filter' }).click();
|
||||
|
||||
// Search results should include these two results
|
||||
// Filter results should include these two results
|
||||
cy.findByRole('heading', { name: 'Admin McAdmin' }).should('exist');
|
||||
cy.findByRole('heading', { name: 'Apple Auth Admin User' }).should(
|
||||
'exist',
|
||||
);
|
||||
cy.findAllByText('Apple Auth Admin User').should('exist');
|
||||
});
|
||||
|
||||
it('Prevents both search and filter widgets being visible at the same time', () => {
|
||||
|
|
@ -65,15 +63,20 @@ describe('User index view', () => {
|
|||
.should('have.attr', 'aria-expanded', 'true');
|
||||
cy.findByRole('button', { name: 'Search' }).should('exist');
|
||||
|
||||
// verify the filter options have now closed
|
||||
// Verify the filter options have now closed
|
||||
cy.get('@filterButton').should('have.attr', 'aria-expanded', 'false');
|
||||
cy.findByRole('combobox').should('not.exist');
|
||||
|
||||
// now re-click filter options and check search options have closed
|
||||
// Now re-click filter options and check search options have closed
|
||||
cy.get('@filterButton').click();
|
||||
cy.get('@searchButton').should('have.attr', 'aria-expanded', 'false');
|
||||
cy.findByRole('button', { name: 'Search' }).should('not.exist');
|
||||
});
|
||||
|
||||
it(`Clicks through to the Member Detail View`, () => {
|
||||
cy.findAllByRole('link', { name: 'Admin McAdmin' }).first().click();
|
||||
cy.url().should('contain', '/admin/users/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -97,6 +100,9 @@ describe('User index view', () => {
|
|||
|
||||
cy.findByRole('button', { name: 'Search' }).click();
|
||||
|
||||
// Correct search result should appear
|
||||
cy.findByRole('heading', { name: 'Admin McAdmin' }).should('exist');
|
||||
|
||||
// The table headers consistitute a row, plus one result
|
||||
cy.findAllByRole('row').should('have.length', 2);
|
||||
});
|
||||
|
|
@ -105,9 +111,18 @@ describe('User index view', () => {
|
|||
cy.findByRole('combobox', { name: 'User role' }).select('super_admin');
|
||||
cy.findByRole('button', { name: 'Filter' }).click();
|
||||
|
||||
// Filter results should include these two results
|
||||
cy.findByRole('heading', { name: 'Admin McAdmin' }).should('exist');
|
||||
cy.findAllByText('Apple Auth Admin User').should('exist');
|
||||
|
||||
// Table header, 'normal' admin and apple auth admin
|
||||
cy.findAllByRole('row').should('have.length', 3);
|
||||
});
|
||||
|
||||
it(`Clicks through to the Member Detail View`, () => {
|
||||
cy.findAllByRole('link', { name: 'Admin McAdmin' }).first().click();
|
||||
cy.url().should('contain', '/admin/users/1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
52
spec/helpers/admin/users_helper_spec.rb
Normal file
52
spec/helpers/admin/users_helper_spec.rb
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
require "rails_helper"
|
||||
|
||||
describe Admin::UsersHelper do
|
||||
describe "#format_last_activity_timestamp" do
|
||||
it "renders the proper 'Last activity' date for a user that was active today" do
|
||||
timestamp = Time.zone.today
|
||||
date = timestamp.strftime("%d %b")
|
||||
formatted_date = helper.format_last_activity_timestamp(timestamp)
|
||||
expect(formatted_date).to eq "Today, #{date}"
|
||||
end
|
||||
|
||||
it "renders the proper 'Last activity' date for a user that was active yesterday" do
|
||||
timestamp = Date.yesterday
|
||||
date = timestamp.strftime("%d %b")
|
||||
formatted_date = helper.format_last_activity_timestamp(timestamp)
|
||||
expect(formatted_date).to eq "Yesterday, #{date}"
|
||||
end
|
||||
|
||||
it "renders the proper 'Last activity' date for a user that was active recently" do
|
||||
timestamp = 11.days.ago
|
||||
date = timestamp.strftime("%d %b, %Y")
|
||||
formatted_date = helper.format_last_activity_timestamp(timestamp)
|
||||
expect(formatted_date).to eq date.to_s
|
||||
end
|
||||
end
|
||||
|
||||
describe "#cascading_high_level_roles" do
|
||||
it "renders the proper role for a Super Admin" do
|
||||
super_admin = create(:user, :super_admin)
|
||||
role = helper.cascading_high_level_roles(super_admin)
|
||||
expect(role).to eq "Super Admin"
|
||||
end
|
||||
|
||||
it "renders the proper role for an Admin" do
|
||||
admin = create(:user, :admin)
|
||||
role = helper.cascading_high_level_roles(admin)
|
||||
expect(role).to eq "Admin"
|
||||
end
|
||||
|
||||
it "renders the proper role for a Resource Admin" do
|
||||
resource_admin = create(:user, :single_resource_admin)
|
||||
role = helper.cascading_high_level_roles(resource_admin)
|
||||
expect(role).to eq "Resource Admin"
|
||||
end
|
||||
|
||||
it "renders the proper role for a user that isn't a Super Admin, Admin, or Resource Admin" do
|
||||
user = create(:user)
|
||||
role = helper.cascading_high_level_roles(user)
|
||||
expect(role).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -798,4 +798,13 @@ RSpec.describe User, type: :model do
|
|||
expect(user.profile).to respond_to(:location)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#last_activity" do
|
||||
it "determines a user's last activity" do
|
||||
Timecop.freeze do
|
||||
user = create(:user, last_comment_at: 1.minute.ago)
|
||||
expect(user.last_activity).to eq(Time.zone.now)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue