diff --git a/app/assets/javascripts/labels/components/delete_label_modal.vue b/app/assets/javascripts/labels/components/delete_label_modal.vue index 1ff0938d086744ab017d8e8d771ea98e7671c860..2be404de1e158a81806723a2eb040fb73b956d5a 100644 --- a/app/assets/javascripts/labels/components/delete_label_modal.vue +++ b/app/assets/javascripts/labels/components/delete_label_modal.vue @@ -56,6 +56,7 @@ export default { </gl-sprintf> </template> <gl-sprintf + v-if="subjectName" :message=" __( `%{strongStart}${labelName}%{strongEnd} will be permanently deleted from ${subjectName}. This cannot be undone.`, @@ -66,6 +67,18 @@ export default { <strong>{{ content }}</strong> </template> </gl-sprintf> + <gl-sprintf + v-else + :message=" + __( + `%{strongStart}${labelName}%{strongEnd} will be permanently deleted. This cannot be undone.`, + ) + " + > + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> <template #modal-footer> <gl-button category="secondary" @click="closeModal">{{ __('Cancel') }}</gl-button> <gl-button diff --git a/app/assets/javascripts/pages/admin/labels/edit/index.js b/app/assets/javascripts/pages/admin/labels/edit/index.js index a3b9c43388a800dd74ef5ebd200112a9358ec9b8..a5eee2857df3f1a086eedc07c475e137c508eb35 100644 --- a/app/assets/javascripts/pages/admin/labels/edit/index.js +++ b/app/assets/javascripts/pages/admin/labels/edit/index.js @@ -1,3 +1,5 @@ import Labels from '~/labels/labels'; +import { initDeleteLabelModal } from '~/labels'; new Labels(); // eslint-disable-line no-new +initDeleteLabelModal(); diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js index e4e377f62fcfd15ea99820ed48a7d3329d3bba77..c032321d039643067a12d00125eb1fa6e2191b92 100644 --- a/app/assets/javascripts/pages/groups/labels/edit/index.js +++ b/app/assets/javascripts/pages/groups/labels/edit/index.js @@ -1,4 +1,6 @@ import Labels from 'ee_else_ce/labels/labels'; +import { initDeleteLabelModal } from '~/labels'; // eslint-disable-next-line no-new new Labels(); +initDeleteLabelModal(); diff --git a/app/assets/javascripts/pages/projects/labels/edit/index.js b/app/assets/javascripts/pages/projects/labels/edit/index.js index c4d7af39767ac6aa2572c1c254b73ad03fc745fc..cb554e3d4da58d1258f249591e817ddad69df025 100644 --- a/app/assets/javascripts/pages/projects/labels/edit/index.js +++ b/app/assets/javascripts/pages/projects/labels/edit/index.js @@ -1,3 +1,5 @@ import Labels from 'ee_else_ce/labels/labels'; +import { initDeleteLabelModal } from '~/labels'; new Labels(); // eslint-disable-line no-new +initDeleteLabelModal(); diff --git a/app/models/label.rb b/app/models/label.rb index a46d6bc5c0ff03100ebe690ef9e0ba62633fc37b..0ebbb5b9bd3432fd40e3c68b101bf82762f8eda9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -260,7 +260,7 @@ def hook_attrs attributes end - def present(attributes) + def present(attributes = {}) super(**attributes.merge(presenter_class: ::LabelPresenter)) end diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb index fafade2828f0dbedaab3375ba96ff294e5704911..8d604f9a0f6d98819ef9abd85b258bb2c78c537a 100644 --- a/app/presenters/label_presenter.rb +++ b/app/presenters/label_presenter.rb @@ -2,7 +2,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated presents ::Label, as: :label - delegate :name, :full_name, to: :label_subject, prefix: :subject + delegate :name, :full_name, to: :label_subject, prefix: :subject, allow_nil: true delegator_override :subject # TODO: Fix `Gitlab::View::Presenter::Delegated#subject` not to override `Label#subject`. @@ -10,6 +10,7 @@ def edit_path case label when GroupLabel then edit_group_label_path(label.group, label) when ProjectLabel then edit_project_label_path(label.project, label) + else edit_admin_label_path(label) end end @@ -17,6 +18,7 @@ def destroy_path case label when GroupLabel then group_label_path(label.group, label) when ProjectLabel then project_label_path(label.project, label) + else admin_label_path(label) end end @@ -43,7 +45,7 @@ def project_label? end def label_subject - @label_subject ||= label.subject + @label_subject ||= label.subject if label.respond_to?(:subject) end private diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml index 9c17735cd14d5ce92c37ad40ff23c4093e2eb206..29f6dc02749f73aafa2ce6bfc45185b182551069 100644 --- a/app/views/shared/labels/_form.html.haml +++ b/app/views/shared/labels/_form.html.haml @@ -26,9 +26,14 @@ %br = _("Or you can choose one of the suggested colors below") = render_suggested_colors - .form-actions + .gl-display-flex.gl-justify-content-space-between.gl-p-5.gl-bg-gray-10.gl-border-t-solid.gl-border-t-gray-100.gl-border-t-1 + %div + - if @label.persisted? + = f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button' + - else + = f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button' + = link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel' - if @label.persisted? - = f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button' - - else - = f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button' - = link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel' + - presented_label = @label.present + %button.btn.btn-danger.gl-button.btn-danger-secondary.js-delete-label-modal-button{ type: 'button', data: { label_name: presented_label.name, subject_name: presented_label.subject_name, destroy_path: presented_label.destroy_path } } + %span.gl-button-text= _('Delete') diff --git a/doc/user/admin_area/img/admin_labels.png b/doc/user/admin_area/img/admin_labels.png deleted file mode 100644 index a9ea059ccf9dd1c8ffa1522db955a867fed40f07..0000000000000000000000000000000000000000 Binary files a/doc/user/admin_area/img/admin_labels.png and /dev/null differ diff --git a/doc/user/admin_area/img/admin_labels_v14_7.png b/doc/user/admin_area/img/admin_labels_v14_7.png new file mode 100644 index 0000000000000000000000000000000000000000..01a4ea0c2ccfee2f74e858cf94f79b1f4b6c90c1 Binary files /dev/null and b/doc/user/admin_area/img/admin_labels_v14_7.png differ diff --git a/doc/user/admin_area/labels.md b/doc/user/admin_area/labels.md index b5dbf835d70d1f44aa1789b8e30ff433a493d1ea..93114186e756f736daeb74bc141c2c152fa197c7 100644 --- a/doc/user/admin_area/labels.md +++ b/doc/user/admin_area/labels.md @@ -7,13 +7,12 @@ type: reference # Labels administration **(FREE SELF)** -In the Admin Area, you can manage labels for the GitLab instance. For more details, see [Labels](../project/labels.md). +To manage labels for the GitLab instance, select **Labels** (**{labels}**) from the Admin Area sidebar. For more details on how to manage labels, see [Labels](../project/labels.md). -## Default Labels +Labels created in the Admin Area are automatically added to new projects. +Updating or adding labels in the Admin Area does not modify labels in existing projects. -Labels created in the Admin Area become available to each _new_ project. - - + <!-- ## Troubleshooting diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 8874512f9c3f40affa5f5e43a2d347003be99d88..7ccc39eeb8bd3fa5fd6a0ae22e9beb4f8d615f46 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -90,9 +90,10 @@ label section of the right sidebar of an issue or a merge request: color value for a specific color. 1. Click **Create**. -Once created, you can edit a label by clicking the pencil (**{pencil}**), or delete -a label by clicking the three dots (**{ellipsis_v}**) next to the **Subscribe** button -and selecting **Delete**. +To edit a label after you create it, select (**{pencil}**). + +To delete a project label, select (**{ellipsis_v}**) next to the **Subscribe** button +and select **Delete** or select **Delete** when you edit a label. WARNING: If you delete a label, it is permanently deleted. All references to the label are removed from the system and you cannot undo the deletion. diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index a9a9416c48bb7bb8fa7f313b63e2753ce8b287cc..f0cef41db69bb32adb23c7b60f2dceb345f9fb03 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -42,4 +42,6 @@ factory :group_label, traits: [:base_label] do group end + + factory :admin_label, traits: [:base_label], class: 'Label' end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb index 72afc040e3d658c12f6f86d8051f9c890ef16300..ba0870a53ae06b4feaa7da881ee12600086e2a2d 100644 --- a/spec/features/admin/admin_labels_spec.rb +++ b/spec/features/admin/admin_labels_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'admin issues labels' do + include Spec::Support::Helpers::ModalHelpers + let!(:bug_label) { Label.create!(title: 'bug', template: true) } let!(:feature_label) { Label.create!(title: 'feature', template: true) } @@ -99,5 +101,19 @@ expect(page).to have_content('fix') end end + + it 'allows user to delete label', :js do + visit edit_admin_label_path(bug_label) + + click_button 'Delete' + + within_modal do + expect(page).to have_content("#{bug_label.title} will be permanently deleted. This cannot be undone.") + + click_link 'Delete label' + end + + expect(page).to have_content('Label was removed') + end end end diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb index 2be7f61eeb960cdd5c0df07b6e9daca71bc68ac0..8e6560af3522cd9dfd89180454636015ecbf166f 100644 --- a/spec/features/groups/labels/edit_spec.rb +++ b/spec/features/groups/labels/edit_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe 'Edit group label' do + include Spec::Support::Helpers::ModalHelpers + let(:user) { create(:user) } let(:group) { create(:group) } let(:label) { create(:group_label, group: group) } @@ -20,4 +22,16 @@ expect(current_path).to eq(root_path) expect(label.reload.title).to eq('new label name') end + + it 'allows user to delete label', :js do + click_button 'Delete' + + within_modal do + expect(page).to have_content("#{label.title} will be permanently deleted from #{group.name}. This cannot be undone.") + + click_link 'Delete label' + end + + expect(page).to have_content("#{label.title} deleted permanently") + end end diff --git a/spec/features/projects/labels/user_edits_labels_spec.rb b/spec/features/projects/labels/user_edits_labels_spec.rb index 8300a1a85422d5680cb79a912cba0b7161555a54..999c238c7b39806f10ed86c0dfa431b1120caec8 100644 --- a/spec/features/projects/labels/user_edits_labels_spec.rb +++ b/spec/features/projects/labels/user_edits_labels_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe "User edits labels" do + include Spec::Support::Helpers::ModalHelpers + let_it_be(:project) { create(:project_empty_repo, :public) } let_it_be(:label) { create(:label, project: project) } let_it_be(:user) { create(:user) } @@ -24,4 +26,16 @@ expect(page).to have_content(new_title).and have_no_content(label.title) end end + + it 'allows user to delete label', :js do + click_button 'Delete' + + within_modal do + expect(page).to have_content("#{label.title} will be permanently deleted from #{project.name}. This cannot be undone.") + + click_link 'Delete label' + end + + expect(page).to have_content('Label was removed') + end end diff --git a/spec/frontend/labels/delete_label_modal_spec.js b/spec/frontend/labels/delete_label_modal_spec.js index c1e6ce879901dd820e579fa5310971a27183fa71..98049538948180cd793ee9659267b82e40088c11 100644 --- a/spec/frontend/labels/delete_label_modal_spec.js +++ b/spec/frontend/labels/delete_label_modal_spec.js @@ -13,6 +13,10 @@ describe('DeleteLabelModal', () => { subjectName: 'GitLab Org', destroyPath: `${TEST_HOST}/2`, }, + { + labelName: 'admin label', + destroyPath: `${TEST_HOST}/3`, + }, ]; beforeEach(() => { @@ -22,8 +26,12 @@ describe('DeleteLabelModal', () => { const button = document.createElement('button'); button.setAttribute('class', 'js-delete-label-modal-button'); button.setAttribute('data-label-name', x.labelName); - button.setAttribute('data-subject-name', x.subjectName); button.setAttribute('data-destroy-path', x.destroyPath); + + if (x.subjectName) { + button.setAttribute('data-subject-name', x.subjectName); + } + button.innerHTML = 'Action'; buttonContainer.appendChild(button); }); @@ -62,6 +70,7 @@ describe('DeleteLabelModal', () => { index ${0} ${1} + ${2} `(`when multiple buttons exist`, ({ index }) => { beforeEach(() => { initDeleteLabelModal(); @@ -69,14 +78,22 @@ describe('DeleteLabelModal', () => { }); it('correct props are passed to gl-modal', () => { - expect(findModal().querySelector('.modal-title').innerHTML).toContain( - buttons[index].labelName, - ); - expect(findModal().querySelector('.modal-body').innerHTML).toContain( - buttons[index].subjectName, - ); + const button = buttons[index]; + + expect(findModal().querySelector('.modal-title').innerHTML).toContain(button.labelName); + + if (button.subjectName) { + expect(findModal().querySelector('.modal-body').textContent).toContain( + `${button.labelName} will be permanently deleted from ${button.subjectName}. This cannot be undone.`, + ); + } else { + expect(findModal().querySelector('.modal-body').textContent).toContain( + `${button.labelName} will be permanently deleted. This cannot be undone.`, + ); + } + expect(findModal().querySelector('.modal-footer .btn-danger').href).toContain( - buttons[index].destroyPath, + button.destroyPath, ); }); }); diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb index bab0d9a10653727f4cbcab1a771f354d2c902c0d..b4d36eaf3401c6cb53dc900c7823182e5a542e79 100644 --- a/spec/presenters/label_presenter_spec.rb +++ b/spec/presenters/label_presenter_spec.rb @@ -10,6 +10,7 @@ let(:label) { build_stubbed(:label, project: project).present(issuable_subject: project) } let(:group_label) { build_stubbed(:group_label, group: group).present(issuable_subject: project) } + let(:admin_label) { build_stubbed(:admin_label).present(issuable_subject: nil) } describe '#edit_path' do context 'with group label' do @@ -23,6 +24,12 @@ it { is_expected.to eq(edit_project_label_path(project, label)) } end + + context 'with an admin label' do + subject { admin_label.edit_path } + + it { is_expected.to eq(edit_admin_label_path(admin_label)) } + end end describe '#destroy_path' do @@ -37,6 +44,12 @@ it { is_expected.to eq(project_label_path(project, label)) } end + + context 'with an admin label' do + subject { admin_label.destroy_path } + + it { is_expected.to eq(admin_label_path(admin_label)) } + end end describe '#filter_path' do @@ -91,6 +104,12 @@ it { is_expected.to eq(label.project.name) } end + + context 'with an admin label' do + subject { admin_label.subject_name } + + it { is_expected.to be_nil } + end end describe '#subject_full_name' do @@ -105,5 +124,11 @@ it { is_expected.to eq(label.project.full_name) } end + + context 'with an admin label' do + subject { admin_label.subject_full_name } + + it { is_expected.to be_nil } + end end end