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

View File

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

View File

@@ -22,6 +22,6 @@
</div> </div>
<div class="small-12 medium-4 column interests" data-equalizer-watch> <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>
</div> </div>

View File

@@ -281,7 +281,7 @@ en:
official_level: "Official level" official_level: "Official level"
phone_number: "Phone number" phone_number: "Phone number"
public_activity: "Keep my list of activities public" 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_debates: "Show debates recommendations"
recommended_proposals: "Show proposals recommendations" recommended_proposals: "Show proposals recommendations"
redeemable_code: "Verification code received via email" redeemable_code: "Verification code received via email"

View File

@@ -281,7 +281,7 @@ es:
official_level: "Nivel del cargo" official_level: "Nivel del cargo"
phone_number: "Teléfono" phone_number: "Teléfono"
public_activity: "Mostrar públicamente mi lista de actividades" 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_debates: "Mostrar recomendaciones en el listado de debates"
recommended_proposals: "Mostrar recomendaciones en el listado de propuestas" recommended_proposals: "Mostrar recomendaciones en el listado de propuestas"
redeemable_code: "Código de verificación por carta (opcional)" 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
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 describe ".find_by_manager_login" do
it "works with a low ID" do it "works with a low ID" do
user = create(:user) user = create(:user)

View File

@@ -231,106 +231,6 @@ describe "Users" do
end end
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 describe "Special comments" do
scenario "comments posted as moderator are not visible in user activity" do scenario "comments posted as moderator are not visible in user activity" do
moderator = create(:administrator).user moderator = create(:administrator).user
@@ -386,117 +286,203 @@ describe "Users" do
describe "Following (public page)" do describe "Following (public page)" do
let(:user) { create(:user) } let(:user) { create(:user) }
scenario "Do not display follows' tab when user is not following any followables" do context "public interests is checked" do
visit user_path(user) let(:user) { create(:user, public_interests: true) }
expect(page).not_to have_content("0 Following") 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_content "1 Following"
expect(page).to have_content "Others follow me"
end
scenario "Gracefully handle followables that have been hidden" do
create(:proposal, followers: [user])
create(:proposal, followers: [user], &:hide)
visit user_path(user)
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])
visit user_path(user)
expect(page).to have_content("1 Following")
end
scenario "Display proposal tab when user is following one proposal at least" do
create(:proposal, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_link("Citizen proposals", href: "#citizen_proposals")
end
scenario "Do not display proposals' tab when user is not following any proposal" do
visit user_path(user, filter: "follows")
expect(page).not_to have_link("Citizen proposals", href: "#citizen_proposals")
end
scenario "Display proposals with link to proposal" do
proposal = create(:proposal, author: user, followers: [user])
login_as user
visit user_path(user, filter: "follows")
expect(page).to have_link "Citizen proposals", href: "#citizen_proposals"
expect(page).to have_content proposal.title
end
scenario "Retired proposals do not have a link to the dashboard" do
proposal = create(:proposal, :retired, author: user)
login_as user
visit user_path(user)
expect(page).to have_content proposal.title
expect(page).not_to have_link "Dashboard"
expect(page).to have_content("Dashboard not available for retired proposals")
end
scenario "Published proposals have a link to the dashboard" do
proposal = create(:proposal, :published, author: user)
login_as user
visit user_path(user)
expect(page).to have_content proposal.title
expect(page).to have_link "Dashboard"
end
end
describe "Budget Investments" do
scenario "Display following tab when user is following one budget investment at least" do
create(:budget_investment, followers: [user])
visit user_path(user)
expect(page).to have_content("1 Following")
end
scenario "Display budget investment tab when user is following one budget investment at least" do
create(:budget_investment, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_link("Investments", href: "#investments")
end
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
budget_investment = create(:budget_investment, author: user, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_link "Investments", href: "#investments"
expect(page).to have_link budget_investment.title
end
end
end end
scenario "Active following tab by default when follows filters selected" do context "public interests is not checked" do
create(:proposal, author: user, followers: [user]) 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") visit user_path(user, filter: "follows")
expect(page).to have_selector(".activity li.is-active", text: "1 Following") expect(page).to have_css "#public_interests"
expect(page).to have_content "Sport"
end end
scenario "Gracefully handle followables that have been hidden" do scenario "Do not display interests when proposal has been destroyed" do
create(:proposal, followers: [user]) proposal = create(:proposal, tag_list: "Sport", followers: [user])
create(:proposal, followers: [user], &:hide) proposal.destroy!
login_as(user)
visit account_path
check "account_public_interests"
click_button "Save changes"
logout
visit user_path(user) visit user_path(user)
expect(page).not_to have_content("Sport")
expect(page).to have_content("1 Following")
end
describe "Proposals" do
scenario "Display following tab when user is following one proposal at least" do
create(:proposal, followers: [user])
visit user_path(user)
expect(page).to have_content("1 Following")
end
scenario "Display proposal tab when user is following one proposal at least" do
create(:proposal, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_link("Citizen proposals", href: "#citizen_proposals")
end
scenario "Do not display proposals' tab when user is not following any proposal" do
visit user_path(user, filter: "follows")
expect(page).not_to have_link("Citizen proposals", href: "#citizen_proposals")
end
scenario "Display proposals with link to proposal" do
proposal = create(:proposal, author: user, followers: [user])
login_as user
visit user_path(user, filter: "follows")
expect(page).to have_link "Citizen proposals", href: "#citizen_proposals"
expect(page).to have_content proposal.title
end
scenario "Retired proposals do not have a link to the dashboard" do
proposal = create(:proposal, :retired, author: user)
login_as user
visit user_path(user)
expect(page).to have_content proposal.title
expect(page).not_to have_link "Dashboard"
expect(page).to have_content("Dashboard not available for retired proposals")
end
scenario "Published proposals have a link to the dashboard" do
proposal = create(:proposal, :published, author: user)
login_as user
visit user_path(user)
expect(page).to have_content proposal.title
expect(page).to have_link "Dashboard"
end
end
describe "Budget Investments" do
scenario "Display following tab when user is following one budget investment at least" do
create(:budget_investment, followers: [user])
visit user_path(user)
expect(page).to have_content("1 Following")
end
scenario "Display budget investment tab when user is following one budget investment at least" do
create(:budget_investment, followers: [user])
visit user_path(user, filter: "follows")
expect(page).to have_link("Investments", href: "#investments")
end
scenario "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")
expect(page).to have_link "Investments", href: "#investments"
expect(page).to have_link budget_investment.title
end
end end
end end