Add topics management (admin area)
## What does this MR do and why? In the course of Issues https://gitlab.com/gitlab-org/gitlab/-/issues/340920, we want to give a boost to the project topics :rocket: In https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70386, the `Projects::Topic` model was extended with an avatar and a description. **As a next step, this MR allows administrators to manage (add/edit) the project topics in the admin area.** :tools: with :heart: at Siemens /cc @bufferoverflow ### Database `Projects::TopicsFinder` execution plans: * List topics - https://console.postgres.ai/shared/b0b599aa-6cbb-432a-b190-a6e23d7b1d13 * List topics, paginated - https://console.postgres.ai/shared/51e05779-b67b-4009-b479-df1595d5943f * Search topics - https://console.postgres.ai/shared/a82ac88d-bc35-4972-b052-a8fd3ca1388c `Gitlab::BackgroundMigration::PopulateTopicsTotalProjectsCountCache` execution plans - these were tested by executing the migration synchronously (on a different branch) and running the migrations testing pipeline - https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71974#note_698490482. Looks good. Migrations output: **Up:** ``` $ bundle exec rails db:migrate == 20211004075629 AddTopicsNameGinIndex: migrating ============================ -- transaction_open?() -> 0.0000s -- index_exists?(:topics, :name, {:name=>"index_topics_on_name_trigram", :using=>:gin, :opclass=>{:name=>:gin_trgm_ops}, :algorithm=>:concurrently}) -> 0.0066s -- execute("SET statement_timeout TO 0") -> 0.0005s -- add_index(:topics, :name, {:name=>"index_topics_on_name_trigram", :using=>:gin, :opclass=>{:name=>:gin_trgm_ops}, :algorithm=>:concurrently}) -> 0.6582s -- execute("RESET statement_timeout") -> 0.0006s == 20211004075629 AddTopicsNameGinIndex: migrated (0.6920s) =================== == 20211006060254 AddTopicsTotalProjectsCountCache: migrating ================= -- add_column(:topics, :total_projects_count, :bigint, {:null=>false, :default=>0}) -> 0.0097s == 20211006060254 AddTopicsTotalProjectsCountCache: migrated (0.0098s) ======== == 20211006060436 SchedulePopulateTopicsTotalProjectsCountCache: migrating ==== -- Scheduled 21 PopulateTopicsTotalProjectsCountCache jobs with a maximum of 10000 records per batch and an interval of 120 seconds. The migration is expected to take at least 2520 seconds. Expect all jobs to have completed after 2021-10-12 01:37:06 UTC." == 20211006060436 SchedulePopulateTopicsTotalProjectsCountCache: migrated (0.2077s) == 20211006122010 AddTopicsTotalProjectsCountIndex: migrating ================= -- transaction_open?() -> 0.0000s -- index_exists?(:topics, [:total_projects_count, :id], {:order=>{:total_projects_count=>:desc}, :name=>"index_topics_total_projects_count", :algorithm=>:concurrently}) -> 0.0015s -- add_index(:topics, [:total_projects_count, :id], {:order=>{:total_projects_count=>:desc}, :name=>"index_topics_total_projects_count", :algorithm=>:concurrently}) -> 0.1684s == 20211006122010 AddTopicsTotalProjectsCountIndex: migrated (0.1728s) ======== ``` **Down:** ``` $ bundle exec rails db:migrate:down VERSION=20211006122010 == 20211006122010 AddTopicsTotalProjectsCountIndex: reverting ================= -- transaction_open?() -> 0.0000s -- indexes(:topics) -> 0.0044s -- execute("SET statement_timeout TO 0") -> 0.0005s -- remove_index(:topics, {:algorithm=>:concurrently, :name=>"index_topics_total_projects_count"}) -> 0.0078s -- execute("RESET statement_timeout") -> 0.0005s == 20211006122010 AddTopicsTotalProjectsCountIndex: reverted (0.0234s) ======== $ bundle exec rails db:migrate:down VERSION=20211006060436 == 20211006060436 SchedulePopulateTopicsTotalProjectsCountCache: reverting ==== == 20211006060436 SchedulePopulateTopicsTotalProjectsCountCache: reverted (0.0000s) $ bundle exec rails db:migrate:down VERSION=20211006060254 == 20211006060254 AddTopicsTotalProjectsCountCache: reverting ================= -- remove_column(:topics, :total_projects_count) -> 0.0029s == 20211006060254 AddTopicsTotalProjectsCountCache: reverted (0.0030s) ======== $ bundle exec rails db:migrate:down VERSION=20211004075629 == 20211004075629 AddTopicsNameGinIndex: reverting ============================ -- transaction_open?() -> 0.0000s -- indexes(:topics) -> 0.0030s -- execute("SET statement_timeout TO 0") -> 0.0006s -- remove_index(:topics, {:algorithm=>:concurrently, :name=>"index_topics_on_name_trigram"}) -> 0.0051s -- execute("RESET statement_timeout") -> 0.0006s == 20211004075629 AddTopicsNameGinIndex: reverted (0.0194s) =================== ``` ## Screenshots or screen recordings ![Topics_Management_Trim](/uploads/4029bcc34569f5b494eb2e28a8ad4da9/Topics_Management_Trim.mp4) ## How to set up and validate locally 1. Sign in as administrator 1. Visit `http://localhost:3000/admin/topics` <details><summary><b>Docs: Administering Topics</b></summary> You can administer all topics in the GitLab instance from the Admin Area's Topics page. To access the Topics page: 1. On the top bar, select **Menu > Admin**. 1. On the left sidebar, select **Overview > Topics**. For each topic, the page displays their name and number of projects labeled with the topic. To edit a topic, click the **Edit** button in that topic's row. To change the sort order, click the sort dropdown and select the desired order. The default sort order is by **Name, ascending**. To search for topics by name, enter your criteria in the search field. The topic search is case insensitive, and applies partial matching. To create a new topic click **New topic**. </details> ## MR acceptance checklist This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability. * [x] I have evaluated the [MR acceptance checklist](https://docs.gitlab.com/ee/development/code_review.html#acceptance-checklist) for this MR.
issue