Open issues count should not include hidden issues for non-admins
What does this MR do?
Epic: &5741
Issue: #327355 (closed)
Follow up to: !66687 (merged)
Display the correct number of issues in the sidebar open issue count.
- If the current user is an admin, the count includes all issues.
- If the current user is a reporter or above (so they can see confidential issues), the count includes confidential issues but not hidden issues.
- If the current user is a Guest or not a member, the count does not include confidential issues or hidden issues.
Cache the issue count so it doesn't have to be constantly recalculated. The count is stored in 3 cache keys:
-
TOTAL_COUNT_KEY
includes confidential and hidden issues (admin) -
TOTAL_COUNT_WITHOUT_HIDDEN_KEY
includes confidential issues but not hidden issues (reporter and above) -
PUBLIC_COUNT_WITHOUT_HIDDEN_KEY
does not include confidential or hidden issues (guest)
Background
Admins can ban suspicious users. Banning a user blocks them and hides their issues from non-admins until the issues can be reviewed and either deemed safe or deleted.
The sidebar open issues count showed the wrong number of issues for non-admins, since hidden issues would be included in the sidebar count but not appear in the issues list. In the screenshot below, this project has a public issue and a hidden issue. When signed in as a non-member, the issue count includes both issues, but should only include the public issue.
Feature implementation plan
The plan: !64728 (comment 623492866)
- Create
banned_users
table.✔ !64728 (merged) - Update the app so that when user is banned new record is inserted in
banned_users
(and the opposite - when user is un-banned, the record is removed).✔ !64728 (merged) - Update issues finder to exclude results from banned users. Put this change behind feature flag.
✔ !66687 (merged) - Enable the feature flag. #330667 (closed)
✔ - Monitor performance (e.g. visualization in Grafana).
- Continue work towards updating
issues.hidden
with background worker, and filter using this column. It's important not to ignore this step, so that we are not caught by surprise whenbanned_users
gets to a size that will affect performance.
DB:
First create some banned users so there are hidden issues:
exec INSERT INTO banned_users SELECT NOW(), NOW(), id FROM users WHERE id > (SELECT id FROM USERS ORDER BY id DESC OFFSET 1000 LIMIT 1) AND (floor(random() * 100)::int > 80)
Admin:
Query: Issue.opened.with_issue_type(Issue::TYPES_FOR_LIST).where(project: projects)
SQL: explain SELECT "issues".* FROM "issues" WHERE "issues"."project_id" = 278964 AND ("issues"."state_id" IN (1)) AND "issues"."issue_type" IN (0, 1) ORDER BY "issues"."created_at" DESC, "issues"."id" DESC LIMIT 20 OFFSET 0
Plan: https://explain.dalibo.com/plan/sAT
Reporter+:
Query: Issue.without_hidden.opened.with_issue_type(Issue::TYPES_FOR_LIST).where(project: projects)
SQL: EXPLAIN SELECT “issues”.* FROM “issues” WHERE (NOT EXISTS (SELECT 1 FROM “banned_users” WHERE (issues.author_id = banned_users.user_id))) AND “issues”.“project_id” = 20 AND (“issues”.“state_id” IN (1)) AND “issues”.“issue_type” IN (0, 1) ORDER BY “issues”.“created_at” DESC, “issues”.“id” DESC LIMIT 20 OFFSET 0
Plan: https://explain.dalibo.com/plan/bw8
Guest/non-member:
Query: Issue.without_hidden.public_only.opened.with_issue_type(Issue::TYPES_FOR_LIST).where(project: projects)
SQL: EXPLAIN SELECT “issues”.* FROM “issues” WHERE (NOT EXISTS (SELECT 1 FROM “banned_users” WHERE (issues.author_id = banned_users.user_id))) AND ( issues.confidential IS NOT TRUE OR (issues.confidential = TRUE AND (issues.author_id = 5966677 OR EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = 5966677 AND issue_id = issues.id) OR EXISTS (SELECT 1 FROM “project_authorizations” WHERE “project_authorizations”.“user_id” = 5966677 AND (project_authorizations.project_id = issues.project_id) AND (project_authorizations.access_level >= 20))))) AND “issues”.“project_id” = 278964 AND (“issues”.“state_id” IN (1)) AND “issues”.“issue_type” IN (0, 1) ORDER BY “issues”.“created_at” DESC, “issues”.“id” DESC LIMIT 20 OFFSET 0
Plan: https://explain.dalibo.com/plan/9NK
Screenshots or Screencasts (strongly suggested)
When admin:
Counts all issues including public, confidential, and hidden issues (4)
When reporter:
Counts public and confidential issues, but not hidden issues (2)
When non-member:
Counts public issues, but not hidden or confidential issues (1)
How to setup and validate locally (strongly suggested)
- Enable the banned users feature in the Rails console:
Feature.enable(:ban_user_feature_flag)
- Create a public project.
- Create a public issue and confidential issue in the project.
- Impersonate a user and create a public issue and confidential issue in the project.
- Stop impersonation.
- Ban the user (user's page > Settings > Ban user)
- View the project page as an admin, a reporter, and a guest. For the admin, the sidebar issues count should be 4 (public, confidential, hidden, hidden+confidential). For the reporter, the sidebar issues count should be 2 (public, confidential). For the guest, the sidebar issues count should be 1 (public).
Does this MR meet the acceptance criteria?
Conformity
-
I have included changelog trailers, or none are needed. (Does this MR need a changelog?) -
I have added/updated documentation, or it's not needed. (Is documentation required?) -
I have properly separated EE content from FOSS, or this MR is FOSS only. (Where should EE code go?) -
I have added information for database reviewers in the MR description, or it's not needed. (Does this MR have database related changes?) -
I have self-reviewed this MR per code review guidelines. -
This MR does not harm performance, or I have asked a reviewer to help assess the performance impact. (Merge request performance guidelines) -
I have followed the style guides. -
This change is backwards compatible across updates, or this does not apply.
Availability and Testing
-
I have added/updated tests following the Testing Guide, or it's not needed. (Consider all test levels. See the Test Planning Process.) -
I have tested this MR in all supported browsers, or it's not needed. -
I have informed the Infrastructure department of a default or new setting change per definition of done, or it's not needed.
Security
Does this MR contain changes to processing or storing of credentials or tokens, authorization and authentication methods or other items described in the security review guidelines? If not, then delete this Security section.
- [-] Label as security and @ mention
@gitlab-com/gl-security/appsec
- [-] The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
- [-] Security reports checked/validated by a reviewer from the AppSec team