Hide what users are following unless they allow it

It could be argued that seeing which proposals a user follows is a good
indicator of which proposals a user has supported, since we're
automatically creating follows for supported proposals since commit
74fbde09f. So now, we're extending the `public_interests` funcionality,
so it only shows elements users are following if they've enabled it.

This is an improvement over using the `public_activity` attribute in two
ways:

* The `public_interests` attribute is disabled by default, so by default
  other users won't be able to see what a user is following
* Who has created proposals/debates/investments/comments is public
  information, while who is following which elements is not; so enabling
  `public_activity` shouldn't imply potentially private information should
  be displayed as well

We've considered removing the `public_interests` attribute completely
and just hiding the "following" page for everyone except its owner, but
keeping it provides more compatibility with existing installations.
This commit is contained in:
Javi Martín
2021-09-04 19:40:48 +02:00
parent 0875c214ba
commit 3cd4f3827e
8 changed files with 287 additions and 213 deletions

View File

@@ -1,6 +1,6 @@
class Users::PublicActivityComponent < ApplicationComponent
attr_reader :user
delegate :authorized_current_user?, :current_path_with_query_params, to: :helpers
delegate :current_user, :valid_interests_access?, :current_path_with_query_params, to: :helpers
def initialize(user)
@user = user
@@ -24,12 +24,16 @@ class Users::PublicActivityComponent < ApplicationComponent
("debates" if feature?(:debates)),
("budget_investments" if feature?(:budgets)),
"comments",
"follows"
("follows" if valid_interests_access?(user))
].compact.select { |filter| send(filter).any? }
end
private
def authorized_current_user?
current_user == user || current_user&.moderator? || current_user&.administrator?
end
def proposals
Proposal.where(author_id: user.id)
end

View File

@@ -1,18 +1,14 @@
class UsersController < ApplicationController
load_and_authorize_resource
helper_method :valid_interests_access?
helper_method :authorized_current_user?
def show
raise CanCan::AccessDenied if params[:filter] == "follows" && !valid_interests_access?(@user)
end
private
def valid_interests_access?
@user.public_interests || authorized_current_user?
end
def authorized_current_user?
@authorized_current_user ||= current_user && (current_user == @user || current_user.moderator? || current_user.administrator?)
def valid_interests_access?(user)
user.public_interests || user == current_user
end
end

View File

@@ -22,6 +22,6 @@
</div>
<div class="small-12 medium-4 column interests" data-equalizer-watch>
<%= render "interests", user: user if valid_interests_access? %>
<%= render "interests", user: user %>
</div>
</div>

View File

@@ -281,7 +281,7 @@ en:
official_level: "Official level"
phone_number: "Phone number"
public_activity: "Keep my list of activities public"
public_interests: "Keep the labels of the elements I follow public"
public_interests: "Keep the elements I follow public"
recommended_debates: "Show debates recommendations"
recommended_proposals: "Show proposals recommendations"
redeemable_code: "Verification code received via email"

View File

@@ -281,7 +281,7 @@ es:
official_level: "Nivel del cargo"
phone_number: "Teléfono"
public_activity: "Mostrar públicamente mi lista de actividades"
public_interests: "Mostrar públicamente las etiquetas de los elementos que sigo"
public_interests: "Mostrar públicamente los elementos que sigo"
recommended_debates: "Mostrar recomendaciones en el listado de debates"
recommended_proposals: "Mostrar recomendaciones en el listado de propuestas"
redeemable_code: "Código de verificación por carta (opcional)"

View File

@@ -0,0 +1,82 @@
require "rails_helper"
describe Users::PublicActivityComponent, controller: UsersController do
around do |example|
with_request_url(Rails.application.routes.url_helpers.user_path(user)) { example.run }
end
describe "follows tab" do
context "public interests is checked" do
let(:user) { create(:user, public_interests: true) }
let(:component) { Users::PublicActivityComponent.new(user) }
it "is displayed for everyone" do
create(:proposal, author: user, followers: [user])
render_inline component
expect(page).to have_content "1 Following"
end
it "is not displayed when the user isn't following any followables" do
create(:proposal, author: user)
render_inline component
expect(page).not_to have_content "Following"
end
it "is the active tab when the follows filters is selected" do
create(:proposal, author: user, followers: [user])
controller.params["filter"] = "follows"
render_inline component
expect(page).to have_selector "li.is-active", text: "1 Following"
end
end
context "public interests is not checked" do
let(:user) { create(:user, public_interests: false) }
let(:component) { Users::PublicActivityComponent.new(user) }
it "is displayed for its owner" do
create(:proposal, followers: [user])
sign_in(user)
render_inline component
expect(page).to have_content "1 Following"
end
it "is not displayed for anonymous users" do
create(:proposal, author: user, followers: [user])
render_inline component
expect(page).to have_content "1 Proposal"
expect(page).not_to have_content "Following"
end
it "is not displayed for other users" do
create(:proposal, author: user, followers: [user])
sign_in(create(:user))
render_inline component
expect(page).to have_content "1 Proposal"
expect(page).not_to have_content "Following"
end
it "is not displayed for administrators" do
create(:proposal, author: user, followers: [user])
sign_in(create(:administrator).user)
render_inline component
expect(page).to have_content "1 Proposal"
expect(page).not_to have_content "Following"
end
end
end
end

View File

@@ -739,6 +739,12 @@ describe User do
end
end
describe "#public_interests" do
it "is false by default" do
expect(User.new.public_interests).to be false
end
end
describe ".find_by_manager_login" do
it "works with a low ID" do
user = create(:user)

View File

@@ -231,106 +231,6 @@ describe "Users" do
end
end
describe "Public interests" do
let(:user) { create(:user) }
scenario "Display interests" do
create(:proposal, tag_list: "Sport", followers: [user])
login_as(user)
visit account_path
check "account_public_interests"
click_button "Save changes"
logout
visit user_path(user, filter: "follows")
expect(page).to have_css "#public_interests"
expect(page).to have_content "Sport"
end
scenario "Not display interests when proposal has been destroyed" do
proposal = create(:proposal, tag_list: "Sport", followers: [user])
proposal.destroy!
login_as(user)
visit account_path
check "account_public_interests"
click_button "Save changes"
logout
visit user_path(user)
expect(page).not_to have_content("Sport")
end
scenario "No visible by default" do
visit user_path(user)
expect(page).to have_content(user.username)
expect(page).not_to have_css("#public_interests")
end
scenario "Is always visible for the owner" do
create(:proposal, tag_list: "Sport", followers: [user])
login_as(user)
visit account_path
uncheck "account_public_interests"
click_button "Save changes"
visit user_path(user, filter: "follows", page: "1")
expect(page).to have_css "#public_interests"
expect(page).to have_content "Tags of elements you follow"
end
scenario "Is always visible for admins" do
create(:proposal, tag_list: "Sport", followers: [user])
login_as(user)
visit account_path
uncheck "account_public_interests"
click_button "Save changes"
logout
login_as(create(:administrator).user)
visit user_path(user, filter: "follows", page: "1")
expect(page).to have_css("#public_interests")
end
scenario "Is always visible for moderators" do
create(:proposal, tag_list: "Sport", followers: [user])
login_as(user)
visit account_path
uncheck "account_public_interests"
click_button "Save changes"
logout
login_as(create(:moderator).user)
visit user_path(user, filter: "follows", page: "1")
expect(page).to have_css("#public_interests")
end
scenario "Should display generic interests title" do
create(:proposal, tag_list: "Sport", followers: [user])
user.update!(public_interests: true)
visit user_path(user, filter: "follows", page: "1")
expect(page).to have_content("Tags of elements this user follows")
end
end
describe "Special comments" do
scenario "comments posted as moderator are not visible in user activity" do
moderator = create(:administrator).user
@@ -386,18 +286,16 @@ describe "Users" do
describe "Following (public page)" do
let(:user) { create(:user) }
scenario "Do not display follows' tab when user is not following any followables" do
visit user_path(user)
context "public interests is checked" do
let(:user) { create(:user, public_interests: true) }
expect(page).not_to have_content("0 Following")
end
scenario "Active following tab by default when follows filters selected" do
create(:proposal, author: user, followers: [user])
scenario "can be accessed by anyone" do
create(:proposal, followers: [user], title: "Others follow me")
visit user_path(user, filter: "follows")
expect(page).to have_selector(".activity li.is-active", text: "1 Following")
expect(page).to have_content "1 Following"
expect(page).to have_content "Others follow me"
end
scenario "Gracefully handle followables that have been hidden" do
@@ -409,6 +307,14 @@ describe "Users" do
expect(page).to have_content("1 Following")
end
scenario "displays generic interests title" do
create(:proposal, tag_list: "Sport", followers: [user])
visit user_path(user, filter: "follows", page: "1")
expect(page).to have_content("Tags of elements this user follows")
end
describe "Proposals" do
scenario "Display following tab when user is following one proposal at least" do
create(:proposal, followers: [user])
@@ -482,14 +388,13 @@ describe "Users" do
expect(page).to have_link("Investments", href: "#investments")
end
scenario "Not display budget investment tab when user is not following any budget investment" do
scenario "Do not display budget investment tab when user is not following any budget investment" do
visit user_path(user, filter: "follows")
expect(page).not_to have_link("Investments", href: "#investments")
end
scenario "Display budget investment with link to budget investment" do
user = create(:user, :level_two)
budget_investment = create(:budget_investment, author: user, followers: [user])
visit user_path(user, filter: "follows")
@@ -500,6 +405,87 @@ describe "Users" do
end
end
context "public interests is not checked" do
let(:user) { create(:user, public_interests: false) }
scenario "can be accessed by its owner" do
create(:proposal, followers: [user], title: "Follow me!")
login_as(user)
visit user_path(user, filter: "follows")
expect(page).to have_content "1 Following"
expect(page).to have_content "Follow me!"
expect(page).to have_content "Tags of elements you follow"
end
scenario "cannot be accessed by anonymous users" do
create(:proposal, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_content "You do not have permission to access this page"
expect(page).to have_current_path root_path
end
scenario "cannot be accessed by other users" do
create(:proposal, followers: [user])
login_as(create(:user))
visit user_path(user, filter: "follows")
expect(page).to have_content "You do not have permission to access this page"
expect(page).to have_current_path root_path
end
scenario "cannot be accessed by administrators" do
create(:proposal, followers: [user])
login_as(create(:administrator).user)
visit user_path(user, filter: "follows")
expect(page).to have_content "You do not have permission to access this page"
expect(page).to have_current_path root_path
end
end
scenario "Display interests" do
create(:proposal, tag_list: "Sport", followers: [user])
login_as(user)
visit account_path
check "account_public_interests"
click_button "Save changes"
logout
visit user_path(user, filter: "follows")
expect(page).to have_css "#public_interests"
expect(page).to have_content "Sport"
end
scenario "Do not display interests when proposal has been destroyed" do
proposal = create(:proposal, tag_list: "Sport", followers: [user])
proposal.destroy!
login_as(user)
visit account_path
check "account_public_interests"
click_button "Save changes"
logout
visit user_path(user)
expect(page).not_to have_content("Sport")
end
end
describe "Initials" do
scenario "display SVG avatars when loaded into the DOM" do
login_as(create(:user))