Skip to content

Project list: fix order by last activity

What does this MR do and why?

A project list is displayed in various places in GitLab (e.g. dashboard or Explore projects page). For each project in the list, it shows when it was last updated (e.g. "Updated 1 week ago"). This text is based on the maximum of three timestamps of a project: last_activity_at, last_repository_updated_at, and updated_at. This is fine and makes sense.

However, there is a problem with sorting: You can sort the list by "Last updated" (or "Oldest updated"), but unfortunately this sorting option only considers the last_activity_at timestamp. This causes that the project list doesn't seem sorted correctly for the user (see screenshots below).

Related issues: #17017, gitlab-foss#39124 (closed)

Note: This MR is a successor to !17254 (closed), which was closed 8 months ago due to no activity. The biggest challenge seemed to be the database performance at that time. Now after two years and some changes in the database environment, I want to make a new attempt to fix this problem.

🛠 with at Siemens

/cc @bufferoverflow

Screenshots

before after

How to set up and validate locally

  1. Visit dashboard: http://localhost:3000/dashboard
  2. Sort project list by Last updated
  3. Visit Explore projects: http://localhost:3000/explore
  4. Sort project list by Last updated

Database Review

Migration

Migration:

$ bundle exec rails db:migrate
== 20211206100701 AddIndexOnProjectLastActivity: migrating ====================
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, "GREATEST(TIMEZONE('UTC', updated_at), TIMEZONE('UTC', last_activity_at), TIMEZONE('UTC', last_repository_updated_at)) DESC NULLS LAST", {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently})
   -> 0.0414s
-- execute("SET statement_timeout TO 0")
   -> 0.0023s
-- add_index(:projects, "GREATEST(TIMEZONE('UTC', updated_at), TIMEZONE('UTC', last_activity_at), TIMEZONE('UTC', last_repository_updated_at)) DESC NULLS LAST", {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently})
   -> 0.0433s
-- execute("RESET statement_timeout")
   -> 0.0016s
== 20211206100701 AddIndexOnProjectLastActivity: migrated (0.1091s) ===========
old

$ rails db:migrate
== 20211206100701 AddIndexOnProjectLastActivity: migrating ====================
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, "GREATEST(updated_at, last_activity_at, last_repository_updated_at) DESC NULLS LAST", {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently})
   -> 0.0249s
-- execute("SET statement_timeout TO 0")
   -> 0.0009s
-- add_index(:projects, "GREATEST(updated_at, last_activity_at, last_repository_updated_at) DESC NULLS LAST", {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently})
   -> 0.0445s
-- execute("RESET statement_timeout")
   -> 0.0510s
== 20211206100701 AddIndexOnProjectLastActivity: migrated (0.1270s) ===========
$ rails db:migrate:up VERSION=20211125133214
== 20211125133214 PrepareAsyncIndexOnProjectLastActivity: migrating ===========
== 20211125133214 PrepareAsyncIndexOnProjectLastActivity: migrated (0.0051s) ==

Rollback:

$ bundle exec rails db:migrate:down VERSION=20211206100701
== 20211206100701 AddIndexOnProjectLastActivity: reverting ====================
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0464s
-- execute("SET statement_timeout TO 0")
   -> 0.0020s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"index_projects_on_last_activity"})
   -> 0.0168s
-- execute("RESET statement_timeout")
   -> 0.0038s
== 20211206100701 AddIndexOnProjectLastActivity: reverted (0.0745s) ===========
old

$ rails db:migrate:down VERSION=20211206100701
== 20211206100701 AddIndexOnProjectLastActivity: reverting ====================
-- transaction_open?()
   -> 0.0001s
-- index_exists?(:projects, "GREATEST(updated_at, last_activity_at, last_repository_updated_at) DESC NULLS LAST", {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently})
   -> 0.0546s
-- execute("SET statement_timeout TO 0")
   -> 0.0066s
-- remove_index(:projects, {:name=>"index_projects_on_last_activity", :algorithm=>:concurrently, :column=>"GREATEST(updated_at, last_activity_at, last_repository_updated_at) DESC NULLS LAST"})
   -> 0.3852s
-- execute("RESET statement_timeout")
   -> 0.0012s
== 20211206100701 AddIndexOnProjectLastActivity: reverted (0.4552s) ===========
$ rails db:migrate:down VERSION=20211125133214
== 20211125133214 PrepareAsyncIndexOnProjectLastActivity: reverting ===========
== 20211125133214 PrepareAsyncIndexOnProjectLastActivity: reverted (0.0031s) ==

Queries

http://localhost:3000/dashboard/projects (before)

WITH "projects_cte" AS MATERIALIZED (
    SELECT
        "projects"."id",
        "projects"."name",
        "projects"."path",
        "projects"."description",
        "projects"."created_at",
        "projects"."updated_at",
        "projects"."creator_id",
        "projects"."namespace_id",
        "projects"."last_activity_at",
        "projects"."import_url",
        "projects"."visibility_level",
        "projects"."archived",
        "projects"."avatar",
        "projects"."merge_requests_template",
        "projects"."star_count",
        "projects"."merge_requests_rebase_enabled",
        "projects"."import_type",
        "projects"."import_source",
        "projects"."approvals_before_merge",
        "projects"."reset_approvals_on_push",
        "projects"."merge_requests_ff_only_enabled",
        "projects"."issues_template",
        "projects"."mirror",
        "projects"."mirror_user_id",
        "projects"."shared_runners_enabled",
        "projects"."runners_token",
        "projects"."build_coverage_regex",
        "projects"."build_allow_git_fetch",
        "projects"."build_timeout",
        "projects"."mirror_trigger_builds",
        "projects"."pending_delete",
        "projects"."public_builds",
        "projects"."last_repository_check_failed",
        "projects"."last_repository_check_at",
        "projects"."only_allow_merge_if_pipeline_succeeds",
        "projects"."has_external_issue_tracker",
        "projects"."repository_storage",
        "projects"."repository_read_only",
        "projects"."request_access_enabled",
        "projects"."has_external_wiki",
        "projects"."ci_config_path",
        "projects"."lfs_enabled",
        "projects"."description_html",
        "projects"."only_allow_merge_if_all_discussions_are_resolved",
        "projects"."repository_size_limit",
        "projects"."printing_merge_request_link_enabled",
        "projects"."auto_cancel_pending_pipelines",
        "projects"."service_desk_enabled",
        "projects"."cached_markdown_version",
        "projects"."delete_error",
        "projects"."last_repository_updated_at",
        "projects"."disable_overriding_approvers_per_merge_request",
        "projects"."storage_version",
        "projects"."resolve_outdated_diff_discussions",
        "projects"."remote_mirror_available_overridden",
        "projects"."only_mirror_protected_branches",
        "projects"."pull_mirror_available_overridden",
        "projects"."jobs_cache_index",
        "projects"."external_authorization_classification_label",
        "projects"."mirror_overwrites_diverged_branches",
        "projects"."pages_https_only",
        "projects"."external_webhook_token",
        "projects"."packages_enabled",
        "projects"."merge_requests_author_approval",
        "projects"."pool_repository_id",
        "projects"."runners_token_encrypted",
        "projects"."bfg_object_map",
        "projects"."detected_repository_languages",
        "projects"."merge_requests_disable_committers_approval",
        "projects"."require_password_to_approve",
        "projects"."emails_disabled",
        "projects"."max_pages_size",
        "projects"."max_artifacts_size",
        "projects"."remove_source_branch_after_merge",
        "projects"."marked_for_deletion_at",
        "projects"."marked_for_deletion_by_user_id",
        "projects"."autoclose_referenced_issues",
        "projects"."suggestion_commit_message",
        "projects"."project_namespace_id"
    FROM
        "projects"
        INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
    WHERE
        "project_authorizations"."user_id" = 1
)
SELECT
    "projects"."id",
    "projects"."name",
    "projects"."path",
    "projects"."description",
    "projects"."created_at",
    "projects"."updated_at",
    "projects"."creator_id",
    "projects"."namespace_id",
    "projects"."last_activity_at",
    "projects"."import_url",
    "projects"."visibility_level",
    "projects"."archived",
    "projects"."avatar",
    "projects"."merge_requests_template",
    "projects"."star_count",
    "projects"."merge_requests_rebase_enabled",
    "projects"."import_type",
    "projects"."import_source",
    "projects"."approvals_before_merge",
    "projects"."reset_approvals_on_push",
    "projects"."merge_requests_ff_only_enabled",
    "projects"."issues_template",
    "projects"."mirror",
    "projects"."mirror_user_id",
    "projects"."shared_runners_enabled",
    "projects"."runners_token",
    "projects"."build_coverage_regex",
    "projects"."build_allow_git_fetch",
    "projects"."build_timeout",
    "projects"."mirror_trigger_builds",
    "projects"."pending_delete",
    "projects"."public_builds",
    "projects"."last_repository_check_failed",
    "projects"."last_repository_check_at",
    "projects"."only_allow_merge_if_pipeline_succeeds",
    "projects"."has_external_issue_tracker",
    "projects"."repository_storage",
    "projects"."repository_read_only",
    "projects"."request_access_enabled",
    "projects"."has_external_wiki",
    "projects"."ci_config_path",
    "projects"."lfs_enabled",
    "projects"."description_html",
    "projects"."only_allow_merge_if_all_discussions_are_resolved",
    "projects"."repository_size_limit",
    "projects"."printing_merge_request_link_enabled",
    "projects"."auto_cancel_pending_pipelines",
    "projects"."service_desk_enabled",
    "projects"."cached_markdown_version",
    "projects"."delete_error",
    "projects"."last_repository_updated_at",
    "projects"."disable_overriding_approvers_per_merge_request",
    "projects"."storage_version",
    "projects"."resolve_outdated_diff_discussions",
    "projects"."remote_mirror_available_overridden",
    "projects"."only_mirror_protected_branches",
    "projects"."pull_mirror_available_overridden",
    "projects"."jobs_cache_index",
    "projects"."external_authorization_classification_label",
    "projects"."mirror_overwrites_diverged_branches",
    "projects"."pages_https_only",
    "projects"."external_webhook_token",
    "projects"."packages_enabled",
    "projects"."merge_requests_author_approval",
    "projects"."pool_repository_id",
    "projects"."runners_token_encrypted",
    "projects"."bfg_object_map",
    "projects"."detected_repository_languages",
    "projects"."merge_requests_disable_committers_approval",
    "projects"."require_password_to_approve",
    "projects"."emails_disabled",
    "projects"."max_pages_size",
    "projects"."max_artifacts_size",
    "projects"."remove_source_branch_after_merge",
    "projects"."marked_for_deletion_at",
    "projects"."marked_for_deletion_by_user_id",
    "projects"."autoclose_referenced_issues",
    "projects"."suggestion_commit_message",
    "projects"."project_namespace_id"
FROM
    "projects_cte" AS "projects"
WHERE
    "projects"."archived" = FALSE
    AND "projects"."pending_delete" = FALSE
ORDER BY
    "projects"."last_activity_at" DESC
LIMIT 20 OFFSET 0
http://localhost:3000/dashboard/projects (after)

WITH "projects_cte" AS MATERIALIZED (
    SELECT
        "projects"."id",
        "projects"."name",
        "projects"."path",
        "projects"."description",
        "projects"."created_at",
        "projects"."updated_at",
        "projects"."creator_id",
        "projects"."namespace_id",
        "projects"."last_activity_at",
        "projects"."import_url",
        "projects"."visibility_level",
        "projects"."archived",
        "projects"."avatar",
        "projects"."merge_requests_template",
        "projects"."star_count",
        "projects"."merge_requests_rebase_enabled",
        "projects"."import_type",
        "projects"."import_source",
        "projects"."approvals_before_merge",
        "projects"."reset_approvals_on_push",
        "projects"."merge_requests_ff_only_enabled",
        "projects"."issues_template",
        "projects"."mirror",
        "projects"."mirror_user_id",
        "projects"."shared_runners_enabled",
        "projects"."runners_token",
        "projects"."build_coverage_regex",
        "projects"."build_allow_git_fetch",
        "projects"."build_timeout",
        "projects"."mirror_trigger_builds",
        "projects"."pending_delete",
        "projects"."public_builds",
        "projects"."last_repository_check_failed",
        "projects"."last_repository_check_at",
        "projects"."only_allow_merge_if_pipeline_succeeds",
        "projects"."has_external_issue_tracker",
        "projects"."repository_storage",
        "projects"."repository_read_only",
        "projects"."request_access_enabled",
        "projects"."has_external_wiki",
        "projects"."ci_config_path",
        "projects"."lfs_enabled",
        "projects"."description_html",
        "projects"."only_allow_merge_if_all_discussions_are_resolved",
        "projects"."repository_size_limit",
        "projects"."printing_merge_request_link_enabled",
        "projects"."auto_cancel_pending_pipelines",
        "projects"."service_desk_enabled",
        "projects"."cached_markdown_version",
        "projects"."delete_error",
        "projects"."last_repository_updated_at",
        "projects"."disable_overriding_approvers_per_merge_request",
        "projects"."storage_version",
        "projects"."resolve_outdated_diff_discussions",
        "projects"."remote_mirror_available_overridden",
        "projects"."only_mirror_protected_branches",
        "projects"."pull_mirror_available_overridden",
        "projects"."jobs_cache_index",
        "projects"."external_authorization_classification_label",
        "projects"."mirror_overwrites_diverged_branches",
        "projects"."pages_https_only",
        "projects"."external_webhook_token",
        "projects"."packages_enabled",
        "projects"."merge_requests_author_approval",
        "projects"."pool_repository_id",
        "projects"."runners_token_encrypted",
        "projects"."bfg_object_map",
        "projects"."detected_repository_languages",
        "projects"."merge_requests_disable_committers_approval",
        "projects"."require_password_to_approve",
        "projects"."emails_disabled",
        "projects"."max_pages_size",
        "projects"."max_artifacts_size",
        "projects"."remove_source_branch_after_merge",
        "projects"."marked_for_deletion_at",
        "projects"."marked_for_deletion_by_user_id",
        "projects"."autoclose_referenced_issues",
        "projects"."suggestion_commit_message",
        "projects"."project_namespace_id"
    FROM
        "projects"
        INNER JOIN "project_authorizations" ON "projects"."id" = "project_authorizations"."project_id"
    WHERE
        "project_authorizations"."user_id" = 1
)
SELECT
    "projects"."id",
    "projects"."name",
    "projects"."path",
    "projects"."description",
    "projects"."created_at",
    "projects"."updated_at",
    "projects"."creator_id",
    "projects"."namespace_id",
    "projects"."last_activity_at",
    "projects"."import_url",
    "projects"."visibility_level",
    "projects"."archived",
    "projects"."avatar",
    "projects"."merge_requests_template",
    "projects"."star_count",
    "projects"."merge_requests_rebase_enabled",
    "projects"."import_type",
    "projects"."import_source",
    "projects"."approvals_before_merge",
    "projects"."reset_approvals_on_push",
    "projects"."merge_requests_ff_only_enabled",
    "projects"."issues_template",
    "projects"."mirror",
    "projects"."mirror_user_id",
    "projects"."shared_runners_enabled",
    "projects"."runners_token",
    "projects"."build_coverage_regex",
    "projects"."build_allow_git_fetch",
    "projects"."build_timeout",
    "projects"."mirror_trigger_builds",
    "projects"."pending_delete",
    "projects"."public_builds",
    "projects"."last_repository_check_failed",
    "projects"."last_repository_check_at",
    "projects"."only_allow_merge_if_pipeline_succeeds",
    "projects"."has_external_issue_tracker",
    "projects"."repository_storage",
    "projects"."repository_read_only",
    "projects"."request_access_enabled",
    "projects"."has_external_wiki",
    "projects"."ci_config_path",
    "projects"."lfs_enabled",
    "projects"."description_html",
    "projects"."only_allow_merge_if_all_discussions_are_resolved",
    "projects"."repository_size_limit",
    "projects"."printing_merge_request_link_enabled",
    "projects"."auto_cancel_pending_pipelines",
    "projects"."service_desk_enabled",
    "projects"."cached_markdown_version",
    "projects"."delete_error",
    "projects"."last_repository_updated_at",
    "projects"."disable_overriding_approvers_per_merge_request",
    "projects"."storage_version",
    "projects"."resolve_outdated_diff_discussions",
    "projects"."remote_mirror_available_overridden",
    "projects"."only_mirror_protected_branches",
    "projects"."pull_mirror_available_overridden",
    "projects"."jobs_cache_index",
    "projects"."external_authorization_classification_label",
    "projects"."mirror_overwrites_diverged_branches",
    "projects"."pages_https_only",
    "projects"."external_webhook_token",
    "projects"."packages_enabled",
    "projects"."merge_requests_author_approval",
    "projects"."pool_repository_id",
    "projects"."runners_token_encrypted",
    "projects"."bfg_object_map",
    "projects"."detected_repository_languages",
    "projects"."merge_requests_disable_committers_approval",
    "projects"."require_password_to_approve",
    "projects"."emails_disabled",
    "projects"."max_pages_size",
    "projects"."max_artifacts_size",
    "projects"."remove_source_branch_after_merge",
    "projects"."marked_for_deletion_at",
    "projects"."marked_for_deletion_by_user_id",
    "projects"."autoclose_referenced_issues",
    "projects"."suggestion_commit_message",
    "projects"."project_namespace_id"
FROM
    "projects_cte" AS "projects"
WHERE
    "projects"."archived" = FALSE
    AND "projects"."pending_delete" = FALSE
ORDER BY
    GREATEST (projects.updated_at, projects.last_activity_at, projects.last_repository_updated_at) DESC
LIMIT 20 OFFSET 0
http://localhost:3000/explore (before)

SELECT
    "projects"."id",
    "projects"."name",
    "projects"."path",
    "projects"."description",
    "projects"."created_at",
    "projects"."updated_at",
    "projects"."creator_id",
    "projects"."namespace_id",
    "projects"."last_activity_at",
    "projects"."import_url",
    "projects"."visibility_level",
    "projects"."archived",
    "projects"."avatar",
    "projects"."merge_requests_template",
    "projects"."star_count",
    "projects"."merge_requests_rebase_enabled",
    "projects"."import_type",
    "projects"."import_source",
    "projects"."approvals_before_merge",
    "projects"."reset_approvals_on_push",
    "projects"."merge_requests_ff_only_enabled",
    "projects"."issues_template",
    "projects"."mirror",
    "projects"."mirror_user_id",
    "projects"."shared_runners_enabled",
    "projects"."runners_token",
    "projects"."build_coverage_regex",
    "projects"."build_allow_git_fetch",
    "projects"."build_timeout",
    "projects"."mirror_trigger_builds",
    "projects"."pending_delete",
    "projects"."public_builds",
    "projects"."last_repository_check_failed",
    "projects"."last_repository_check_at",
    "projects"."only_allow_merge_if_pipeline_succeeds",
    "projects"."has_external_issue_tracker",
    "projects"."repository_storage",
    "projects"."repository_read_only",
    "projects"."request_access_enabled",
    "projects"."has_external_wiki",
    "projects"."ci_config_path",
    "projects"."lfs_enabled",
    "projects"."description_html",
    "projects"."only_allow_merge_if_all_discussions_are_resolved",
    "projects"."repository_size_limit",
    "projects"."printing_merge_request_link_enabled",
    "projects"."auto_cancel_pending_pipelines",
    "projects"."service_desk_enabled",
    "projects"."cached_markdown_version",
    "projects"."delete_error",
    "projects"."last_repository_updated_at",
    "projects"."disable_overriding_approvers_per_merge_request",
    "projects"."storage_version",
    "projects"."resolve_outdated_diff_discussions",
    "projects"."remote_mirror_available_overridden",
    "projects"."only_mirror_protected_branches",
    "projects"."pull_mirror_available_overridden",
    "projects"."jobs_cache_index",
    "projects"."external_authorization_classification_label",
    "projects"."mirror_overwrites_diverged_branches",
    "projects"."pages_https_only",
    "projects"."external_webhook_token",
    "projects"."packages_enabled",
    "projects"."merge_requests_author_approval",
    "projects"."pool_repository_id",
    "projects"."runners_token_encrypted",
    "projects"."bfg_object_map",
    "projects"."detected_repository_languages",
    "projects"."merge_requests_disable_committers_approval",
    "projects"."require_password_to_approve",
    "projects"."emails_disabled",
    "projects"."max_pages_size",
    "projects"."max_artifacts_size",
    "projects"."remove_source_branch_after_merge",
    "projects"."marked_for_deletion_at",
    "projects"."marked_for_deletion_by_user_id",
    "projects"."autoclose_referenced_issues",
    "projects"."suggestion_commit_message",
    "projects"."project_namespace_id"
FROM
    "projects"
WHERE (EXISTS (
        SELECT
            1
        FROM
            "project_authorizations"
        WHERE
            "project_authorizations"."user_id" = 1
            AND (project_authorizations.project_id = projects.id))
        OR projects.visibility_level IN (0, 10, 20))
AND "projects"."archived" = FALSE
ORDER BY
    "projects"."last_activity_at" DESC
LIMIT 21 OFFSET 0
http://localhost:3000/explore (after)

SELECT
    "projects"."id",
    "projects"."name",
    "projects"."path",
    "projects"."description",
    "projects"."created_at",
    "projects"."updated_at",
    "projects"."creator_id",
    "projects"."namespace_id",
    "projects"."last_activity_at",
    "projects"."import_url",
    "projects"."visibility_level",
    "projects"."archived",
    "projects"."avatar",
    "projects"."merge_requests_template",
    "projects"."star_count",
    "projects"."merge_requests_rebase_enabled",
    "projects"."import_type",
    "projects"."import_source",
    "projects"."approvals_before_merge",
    "projects"."reset_approvals_on_push",
    "projects"."merge_requests_ff_only_enabled",
    "projects"."issues_template",
    "projects"."mirror",
    "projects"."mirror_user_id",
    "projects"."shared_runners_enabled",
    "projects"."runners_token",
    "projects"."build_coverage_regex",
    "projects"."build_allow_git_fetch",
    "projects"."build_timeout",
    "projects"."mirror_trigger_builds",
    "projects"."pending_delete",
    "projects"."public_builds",
    "projects"."last_repository_check_failed",
    "projects"."last_repository_check_at",
    "projects"."only_allow_merge_if_pipeline_succeeds",
    "projects"."has_external_issue_tracker",
    "projects"."repository_storage",
    "projects"."repository_read_only",
    "projects"."request_access_enabled",
    "projects"."has_external_wiki",
    "projects"."ci_config_path",
    "projects"."lfs_enabled",
    "projects"."description_html",
    "projects"."only_allow_merge_if_all_discussions_are_resolved",
    "projects"."repository_size_limit",
    "projects"."printing_merge_request_link_enabled",
    "projects"."auto_cancel_pending_pipelines",
    "projects"."service_desk_enabled",
    "projects"."cached_markdown_version",
    "projects"."delete_error",
    "projects"."last_repository_updated_at",
    "projects"."disable_overriding_approvers_per_merge_request",
    "projects"."storage_version",
    "projects"."resolve_outdated_diff_discussions",
    "projects"."remote_mirror_available_overridden",
    "projects"."only_mirror_protected_branches",
    "projects"."pull_mirror_available_overridden",
    "projects"."jobs_cache_index",
    "projects"."external_authorization_classification_label",
    "projects"."mirror_overwrites_diverged_branches",
    "projects"."pages_https_only",
    "projects"."external_webhook_token",
    "projects"."packages_enabled",
    "projects"."merge_requests_author_approval",
    "projects"."pool_repository_id",
    "projects"."runners_token_encrypted",
    "projects"."bfg_object_map",
    "projects"."detected_repository_languages",
    "projects"."merge_requests_disable_committers_approval",
    "projects"."require_password_to_approve",
    "projects"."emails_disabled",
    "projects"."max_pages_size",
    "projects"."max_artifacts_size",
    "projects"."remove_source_branch_after_merge",
    "projects"."marked_for_deletion_at",
    "projects"."marked_for_deletion_by_user_id",
    "projects"."autoclose_referenced_issues",
    "projects"."suggestion_commit_message",
    "projects"."project_namespace_id"
FROM
    "projects"
WHERE (EXISTS (
        SELECT
            1
        FROM
            "project_authorizations"
        WHERE
            "project_authorizations"."user_id" = 1
            AND (project_authorizations.project_id = projects.id))
        OR projects.visibility_level IN (0, 10, 20))
AND "projects"."archived" = FALSE
ORDER BY
    GREATEST (projects.updated_at, projects.last_activity_at, projects.last_repository_updated_at) DESC
LIMIT 21 OFFSET 0

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Jonas Wälter

Merge request reports