diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 62af141fbc5b2fbf0e7e4b8a728e9327e9b82f68..b37d54667575711a5e64cb8b2c2b6ea21d37935b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -436,6 +436,7 @@ def project_params_attributes :request_access_enabled, :runners_token, :tag_list, + :topics, :visibility_level, :template_name, :template_project_id, diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 97ea7d8754593852de714b934ddb634ef6dc6271..8beb34a4626969dae1a178e967c8e7118dd47c6f 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -11,6 +11,8 @@ def initialize(user, params) @initialize_with_readme = Gitlab::Utils.to_boolean(@params.delete(:initialize_with_readme)) @import_data = @params.delete(:import_data) @relations_block = @params.delete(:relations_block) + + build_topics end def execute @@ -261,6 +263,14 @@ def project_visibility .new(current_user, @project, project_params: { import_data: @import_data }) .level_restricted? end + + def build_topics + topics = params.delete(:topics) + tag_list = params.delete(:tag_list) + topic_list = topics || tag_list + + params[:topic_list] ||= topic_list if topic_list + end end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 541b333aae30224133072b8c8d5e909125f83ff3..4351a66351d135b835dd2a7a2c522124da92654b 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -8,6 +8,7 @@ class UpdateService < BaseService ValidationError = Class.new(StandardError) def execute + build_topics remove_unallowed_params validate! @@ -167,6 +168,14 @@ def changing_repository_storage? project.repository_storage != new_repository_storage && can?(current_user, :change_repository_storage, project) end + + def build_topics + topics = params.delete(:topics) + tag_list = params.delete(:tag_list) + topic_list = topics || tag_list + + params[:topic_list] ||= topic_list if topic_list + end end end diff --git a/app/views/projects/settings/_general.html.haml b/app/views/projects/settings/_general.html.haml index 845fb299b74e3f17ee54065095e9d8602479a459..0891e3e052697693d01816086e44b67b99e07230 100644 --- a/app/views/projects/settings/_general.html.haml +++ b/app/views/projects/settings/_general.html.haml @@ -15,8 +15,8 @@ .row .form-group.col-md-9 - = f.label :tag_list, _('Topics (optional)'), class: 'label-bold' - = f.text_field :tag_list, value: @project.tag_list.join(', '), maxlength: 2000, class: "form-control gl-form-input" + = f.label :topics, _('Topics (optional)'), class: 'label-bold' + = f.text_field :topics, value: @project.topic_list.join(', '), maxlength: 2000, class: "form-control gl-form-input" %p.form-text.text-muted= _('Separate topics with commas.') = render_if_exists 'compliance_management/compliance_framework/project_settings', f: f diff --git a/doc/api/projects.md b/doc/api/projects.md index 6fd9ca81b8c2dba7ffdc7e7d6bb1ffc7ed84a5a8..4f5233e212685523316bc64b7b2ef83f78f680ed 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1165,9 +1165,10 @@ POST /projects | `show_default_award_emojis` | boolean | **{dotted-circle}** No | Show default award emojis. | | `snippets_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `snippets_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable snippets for this project. Use `snippets_access_level` instead. | -| `tag_list` | array | **{dotted-circle}** No | The list of tags for a project; put array of tags, that should be finally assigned to a project. | +| `tag_list` | array | **{dotted-circle}** No | _([Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0)_ The list of tags for a project; put array of tags, that should be finally assigned to a project. Use `topics` instead. | | `template_name` | string | **{dotted-circle}** No | When used without `use_custom_template`, name of a [built-in project template](../user/project/working_with_projects.md#built-in-templates). When used with `use_custom_template`, name of a custom project template. | | `template_project_id` **(PREMIUM)** | integer | **{dotted-circle}** No | When used with `use_custom_template`, project ID of a custom project template. This is preferable to using `template_name` since `template_name` may be ambiguous. | +| `topics` | array | **{dotted-circle}** No | The list of topics for a project; put array of topics, that should be finally assigned to a project. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0.)_ | | `use_custom_template` **(PREMIUM)** | boolean | **{dotted-circle}** No | Use either custom [instance](../user/admin_area/custom_project_templates.md) or [group](../user/group/custom_project_templates.md) (with `group_with_project_templates_id`) project template. | | `visibility` | string | **{dotted-circle}** No | See [project visibility level](#project-visibility-level). | | `wiki_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | @@ -1239,8 +1240,9 @@ POST /projects/user/:user_id | `snippets_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `snippets_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable snippets for this project. Use `snippets_access_level` instead. | | `suggestion_commit_message` | string | **{dotted-circle}** No | The commit message used to apply merge request [suggestions](../user/project/merge_requests/reviews/suggestions.md). | -| `tag_list` | array | **{dotted-circle}** No | The list of tags for a project; put array of tags, that should be finally assigned to a project. | +| `tag_list` | array | **{dotted-circle}** No | _([Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0)_ The list of tags for a project; put array of tags, that should be finally assigned to a project. Use `topics` instead. | | `template_name` | string | **{dotted-circle}** No | When used without `use_custom_template`, name of a [built-in project template](../user/project/working_with_projects.md#built-in-templates). When used with `use_custom_template`, name of a custom project template. | +| `topics` | array | **{dotted-circle}** No | The list of topics for the project. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0.)_ | | `use_custom_template` **(PREMIUM)** | boolean | **{dotted-circle}** No | Use either custom [instance](../user/admin_area/custom_project_templates.md) or [group](../user/group/custom_project_templates.md) (with `group_with_project_templates_id`) project template. | | `visibility` | string | **{dotted-circle}** No | See [project visibility level](#project-visibility-level). | | `wiki_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | @@ -1317,7 +1319,8 @@ PUT /projects/:id | `snippets_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `snippets_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable snippets for this project. Use `snippets_access_level` instead. | | `suggestion_commit_message` | string | **{dotted-circle}** No | The commit message used to apply merge request suggestions. | -| `tag_list` | array | **{dotted-circle}** No | The list of tags for a project; put array of tags, that should be finally assigned to a project. | +| `tag_list` | array | **{dotted-circle}** No | _([Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0)_ The list of tags for a project; put array of tags, that should be finally assigned to a project. Use `topics` instead. | +| `topics` | array | **{dotted-circle}** No | The list of topics for the project. This replaces any existing topics that are already added to the project. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/328226) in GitLab 14.0.)_ | | `visibility` | string | **{dotted-circle}** No | See [project visibility level](#project-visibility-level). | | `wiki_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `wiki_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable wiki for this project. Use `wiki_access_level` instead. | diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index d9c0b4f67c854a7a72f6bb85cf3c01be332a4b70..e2897ba6a3ced97a2f630071cd1bf1090fef0467 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -51,7 +51,8 @@ module ProjectsHelpers optional :only_allow_merge_if_pipeline_succeeds, type: Boolean, desc: 'Only allow to merge if builds succeed' optional :allow_merge_on_skipped_pipeline, type: Boolean, desc: 'Allow to merge if pipeline is skipped' optional :only_allow_merge_if_all_discussions_are_resolved, type: Boolean, desc: 'Only allow to merge if all discussions are resolved' - optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of tags for a project' + optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Deprecated: Use :topics instead' + optional :topics, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The list of topics for a project' # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 optional :avatar, type: File, desc: 'Avatar image for project' # rubocop:disable Scalability/FileUploads optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line' @@ -146,6 +147,7 @@ def self.update_params_at_least_one_of :shared_runners_enabled, :snippets_access_level, :tag_list, + :topics, :visibility, :wiki_access_level, :avatar, diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e103aa3d6dee576d7fb0a97ab70fef7bb03e4c8b..5a76495327ddf4a54202507a9479bcfae22f0db7 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1032,7 +1032,7 @@ expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/-/blob/master/README.md") end - it 'sets tag list to a project' do + it 'sets tag list to a project (deprecated)' do project = attributes_for(:project, tag_list: %w[tagFirst tagSecond]) post api('/projects', user), params: project @@ -1040,6 +1040,14 @@ expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond]) end + it 'sets topics to a project' do + project = attributes_for(:project, topics: %w[topic1 topics2]) + + post api('/projects', user), params: project + + expect(json_response['tag_list']).to eq(%w[topic1 topics2]) + end + it 'uploads avatar for project a project' do project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif')) @@ -3009,6 +3017,26 @@ def failure_message(diff) expect(json_response['auto_devops_enabled']).to eq(false) end + + it 'updates topics using tag_list (deprecated)' do + project_param = { tag_list: 'topic1' } + + put api("/projects/#{project3.id}", user), params: project_param + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['tag_list']).to eq(%w[topic1]) + end + + it 'updates topics' do + project_param = { topics: 'topic2' } + + put api("/projects/#{project3.id}", user), params: project_param + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['tag_list']).to eq(%w[topic2]) + end end context 'when authenticated as project maintainer' do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 6c0f36ededa9469782578a756639041de6235a11..753c89eb98149c281360e83227d6c00e3740ef70 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -82,6 +82,34 @@ end end + describe 'topics' do + subject(:project) { create_project(user, opts) } + + context "with 'topics' parameter" do + let(:opts) { { topics: 'topics' } } + + it 'keeps them as specified' do + expect(project.topic_list).to eq(%w[topics]) + end + end + + context "with 'topic_list' parameter" do + let(:opts) { { topic_list: 'topic_list' } } + + it 'keeps them as specified' do + expect(project.topic_list).to eq(%w[topic_list]) + end + end + + context "with 'tag_list' parameter (deprecated)" do + let(:opts) { { tag_list: 'tag_list' } } + + it 'keeps them as specified' do + expect(project.topic_list).to eq(%w[tag_list]) + end + end + end + context 'user namespace' do it do project = create_project(user, opts) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index b9e909e8615ac5f04dd47494f7245e98a97c1a67..e1b22da2e61f6b74b78d2c00f7329312f695408c 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -587,6 +587,31 @@ it_behaves_like 'the transfer was not scheduled' end end + + describe 'when updating topics' do + let(:project) { create(:project, topic_list: 'topic1, topic2') } + + it 'update using topics' do + result = update_project(project, user, { topics: 'topics' }) + + expect(result[:status]).to eq(:success) + expect(project.topic_list).to eq(%w[topics]) + end + + it 'update using topic_list' do + result = update_project(project, user, { topic_list: 'topic_list' }) + + expect(result[:status]).to eq(:success) + expect(project.topic_list).to eq(%w[topic_list]) + end + + it 'update using tag_list (deprecated)' do + result = update_project(project, user, { tag_list: 'tag_list' }) + + expect(result[:status]).to eq(:success) + expect(project.topic_list).to eq(%w[tag_list]) + end + end end describe '#run_auto_devops_pipeline?' do