Skip to content
代码片段 群组 项目
提交 6acf0815 编辑于 作者: Miguel Rincon's avatar Miguel Rincon
浏览文件

Merge branch '408676-adjust-label-close' into 'master'

Remove label close button when lock on merge

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130681



Merged-by: default avatarMiguel Rincon <mrincon@gitlab.com>
Reviewed-by: default avatarSimon Knox <simon@gitlab.com>
Reviewed-by: default avatarMiguel Rincon <mrincon@gitlab.com>
Reviewed-by: default avatarDonald Cook <dcook@gitlab.com>
Co-authored-by: default avatarDonald Cook <dcook@gitlab.com>
Co-authored-by: default avatarBrett Walker <bwalker@gitlab.com>
No related branches found
No related tags found
无相关合并请求
显示
105 个添加20 个删除
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { VARIANT_EMBEDDED } from '~/sidebar/components/labels/labels_select_widget/constants'; import { VARIANT_EMBEDDED } from '~/sidebar/components/labels/labels_select_widget/constants';
import { WORKSPACE_PROJECT } from '~/issues/constants'; import { WORKSPACE_PROJECT } from '~/issues/constants';
import IssuableLabelSelector from '~/vue_shared/issuable/create/components/issuable_label_selector.vue'; import IssuableLabelSelector from '~/vue_shared/issuable/create/components/issuable_label_selector.vue';
...@@ -25,6 +26,7 @@ export default () => { ...@@ -25,6 +26,7 @@ export default () => {
issuableType, issuableType,
labelsFilterBasePath, labelsFilterBasePath,
labelsManagePath, labelsManagePath,
supportsLockOnMerge,
} = el.dataset; } = el.dataset;
return new Vue({ return new Vue({
...@@ -46,6 +48,7 @@ export default () => { ...@@ -46,6 +48,7 @@ export default () => {
variant: VARIANT_EMBEDDED, variant: VARIANT_EMBEDDED,
workspaceType: WORKSPACE_PROJECT, workspaceType: WORKSPACE_PROJECT,
toggleAttrs: { 'data-testid': 'issuable-label-dropdown' }, toggleAttrs: { 'data-testid': 'issuable-label-dropdown' },
supportsLockOnMerge: parseBoolean(supportsLockOnMerge),
}, },
render(createElement) { render(createElement) {
return createElement(IssuableLabelSelector); return createElement(IssuableLabelSelector);
......
...@@ -27,6 +27,11 @@ export default { ...@@ -27,6 +27,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
supportsLockOnMerge: {
type: Boolean,
required: false,
default: false,
},
labelsFilterBasePath: { labelsFilterBasePath: {
type: String, type: String,
required: true, required: true,
...@@ -67,6 +72,12 @@ export default { ...@@ -67,6 +72,12 @@ export default {
scopedLabel(label) { scopedLabel(label) {
return this.allowScopedLabels && isScopedLabel(label); return this.allowScopedLabels && isScopedLabel(label);
}, },
isLabelLocked(label) {
return label.lockOnMerge && this.supportsLockOnMerge;
},
showCloseButton(label) {
return this.allowLabelRemove && !this.isLabelLocked(label);
},
removeLabel(labelId) { removeLabel(labelId) {
this.$emit('onLabelRemove', labelId); this.$emit('onLabelRemove', labelId);
}, },
...@@ -115,7 +126,7 @@ export default { ...@@ -115,7 +126,7 @@ export default {
:background-color="label.color" :background-color="label.color"
:target="labelFilterUrl(label)" :target="labelFilterUrl(label)"
:scoped="scopedLabel(label)" :scoped="scopedLabel(label)"
:show-close-button="allowLabelRemove" :show-close-button="showCloseButton(label)"
:disabled="disableLabels" :disabled="disableLabels"
tooltip-placement="top" tooltip-placement="top"
@close="removeLabel(label.id)" @close="removeLabel(label.id)"
......
...@@ -22,6 +22,11 @@ export default { ...@@ -22,6 +22,11 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
supportsLockOnMerge: {
type: Boolean,
required: false,
default: false,
},
labelsFilterBasePath: { labelsFilterBasePath: {
type: String, type: String,
required: true, required: true,
...@@ -42,9 +47,15 @@ export default { ...@@ -42,9 +47,15 @@ export default {
return `${basePath}?${filterParam}[]=${encodeURIComponent(title)}`; return `${basePath}?${filterParam}[]=${encodeURIComponent(title)}`;
}, },
showScopedLabel(label) { scopedLabel(label) {
return this.allowScopedLabels && isScopedLabel(label); return this.allowScopedLabels && isScopedLabel(label);
}, },
isLabelLocked(label) {
return label.lockOnMerge && this.supportsLockOnMerge;
},
showCloseButton(label) {
return this.allowLabelRemove && !this.isLabelLocked(label);
},
removeLabel(labelId) { removeLabel(labelId) {
this.$emit('onLabelRemove', labelId); this.$emit('onLabelRemove', labelId);
}, },
...@@ -63,8 +74,8 @@ export default { ...@@ -63,8 +74,8 @@ export default {
:description="label.description" :description="label.description"
:background-color="label.color" :background-color="label.color"
:target="buildFilterUrl(label)" :target="buildFilterUrl(label)"
:scoped="showScopedLabel(label)" :scoped="scopedLabel(label)"
:show-close-button="allowLabelRemove" :show-close-button="showCloseButton(label)"
:disabled="disabled" :disabled="disabled"
tooltip-placement="top" tooltip-placement="top"
@close="removeLabel(label.id)" @close="removeLabel(label.id)"
......
...@@ -8,6 +8,7 @@ query mergeRequestLabels($fullPath: ID!, $iid: String!) { ...@@ -8,6 +8,7 @@ query mergeRequestLabels($fullPath: ID!, $iid: String!) {
labels { labels {
nodes { nodes {
...Label ...Label
lockOnMerge
} }
} }
} }
......
...@@ -52,6 +52,11 @@ export default { ...@@ -52,6 +52,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
supportsLockOnMerge: {
type: Boolean,
required: false,
default: false,
},
showEmbeddedLabelsList: { showEmbeddedLabelsList: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -151,6 +156,9 @@ export default { ...@@ -151,6 +156,9 @@ export default {
isLabelListEnabled() { isLabelListEnabled() {
return this.showEmbeddedLabelsList && isDropdownVariantEmbedded(this.variant); return this.showEmbeddedLabelsList && isDropdownVariantEmbedded(this.variant);
}, },
isLockOnMergeSupported() {
return this.enforceLockedLabelsOnMerge;
},
}, },
apollo: { apollo: {
issuable: { issuable: {
...@@ -376,6 +384,7 @@ export default { ...@@ -376,6 +384,7 @@ export default {
:disable-labels="labelsSelectInProgress" :disable-labels="labelsSelectInProgress"
:selected-labels="issuableLabels" :selected-labels="issuableLabels"
:allow-label-remove="allowLabelRemove" :allow-label-remove="allowLabelRemove"
:supports-lock-on-merge="isLockOnMergeSupported"
:labels-filter-base-path="labelsFilterBasePath" :labels-filter-base-path="labelsFilterBasePath"
:labels-filter-param="labelsFilterParam" :labels-filter-param="labelsFilterParam"
@onLabelRemove="handleLabelRemove" @onLabelRemove="handleLabelRemove"
...@@ -389,6 +398,7 @@ export default { ...@@ -389,6 +398,7 @@ export default {
:disable-labels="labelsSelectInProgress" :disable-labels="labelsSelectInProgress"
:selected-labels="issuableLabels" :selected-labels="issuableLabels"
:allow-label-remove="allowLabelRemove" :allow-label-remove="allowLabelRemove"
:supports-lock-on-merge="isLockOnMergeSupported"
:labels-filter-base-path="labelsFilterBasePath" :labels-filter-base-path="labelsFilterBasePath"
:labels-filter-param="labelsFilterParam" :labels-filter-param="labelsFilterParam"
class="gl-mb-2" class="gl-mb-2"
...@@ -440,6 +450,7 @@ export default { ...@@ -440,6 +450,7 @@ export default {
:disabled="labelsSelectInProgress" :disabled="labelsSelectInProgress"
:selected-labels="issuableLabels" :selected-labels="issuableLabels"
:allow-label-remove="allowLabelRemove" :allow-label-remove="allowLabelRemove"
:supports-lock-on-merge="isLockOnMergeSupported"
:labels-filter-base-path="labelsFilterBasePath" :labels-filter-base-path="labelsFilterBasePath"
:labels-filter-param="labelsFilterParam" :labels-filter-param="labelsFilterParam"
@onLabelRemove="handleLabelRemove" @onLabelRemove="handleLabelRemove"
......
...@@ -355,6 +355,7 @@ export function mountSidebarLabelsWidget() { ...@@ -355,6 +355,7 @@ export function mountSidebarLabelsWidget() {
workspaceType: WORKSPACE_PROJECT, workspaceType: WORKSPACE_PROJECT,
attrWorkspacePath: el.dataset.projectPath, attrWorkspacePath: el.dataset.projectPath,
labelCreateType: WORKSPACE_PROJECT, labelCreateType: WORKSPACE_PROJECT,
enforceLockedLabelsOnMerge: gon.features.enforceLockedLabelsOnMerge,
}, },
class: ['block labels js-labels-block'], class: ['block labels js-labels-block'],
scopedSlots: { scopedSlots: {
......
...@@ -18,6 +18,7 @@ export default { ...@@ -18,6 +18,7 @@ export default {
'initialLabels', 'initialLabels',
'issuableType', 'issuableType',
'labelType', 'labelType',
'supportsLockOnMerge',
'variant', 'variant',
'workspaceType', 'workspaceType',
], ],
...@@ -76,6 +77,7 @@ export default { ...@@ -76,6 +77,7 @@ export default {
:issuable-type="issuableType" :issuable-type="issuableType"
:label-create-type="labelType" :label-create-type="labelType"
:selected-labels="selectedLabels" :selected-labels="selectedLabels"
:supports-lock-on-merge="supportsLockOnMerge"
@updateSelectedLabels="handleUpdateSelectedLabels" @updateSelectedLabels="handleUpdateSelectedLabels"
@onLabelRemove="handleLabelRemove" @onLabelRemove="handleLabelRemove"
> >
......
...@@ -4,6 +4,9 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -4,6 +4,9 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
before_action :check_merge_requests_available! before_action :check_merge_requests_available!
before_action :merge_request before_action :merge_request
before_action :authorize_read_merge_request! before_action :authorize_read_merge_request!
before_action do
push_force_frontend_feature_flag(:enforce_locked_labels_on_merge, project&.supports_lock_on_merge?)
end
feature_category :code_review_workflow feature_category :code_review_workflow
......
...@@ -248,7 +248,8 @@ def issuable_label_selector_data(project, issuable) ...@@ -248,7 +248,8 @@ def issuable_label_selector_data(project, issuable)
title: label.title, title: label.title,
description: label.description, description: label.description,
color: label.color, color: label.color,
text_color: label.text_color text_color: label.text_color,
lock_on_merge: label.lock_on_merge
} }
end end
...@@ -265,7 +266,8 @@ def issuable_label_selector_data(project, issuable) ...@@ -265,7 +266,8 @@ def issuable_label_selector_data(project, issuable)
initial_labels: initial_labels.to_json, initial_labels: initial_labels.to_json,
issuable_type: issuable.issuable_type, issuable_type: issuable.issuable_type,
labels_filter_base_path: filter_base_path, labels_filter_base_path: filter_base_path,
labels_manage_path: project_labels_path(project) labels_manage_path: project_labels_path(project),
supports_lock_on_merge: issuable.supports_lock_on_merge?.to_s
} }
end end
......
...@@ -3,14 +3,15 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,14 +3,15 @@ import { shallowMount } from '@vue/test-utils';
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue'; import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
import { mockRegularLabel, mockScopedLabel } from './mock_data'; import { mockRegularLabel, mockScopedLabel, mockLockedLabel } from './mock_data';
describe('DropdownValue', () => { describe('DropdownValue', () => {
let wrapper; let wrapper;
const findAllLabels = () => wrapper.findAllComponents(GlLabel); const findAllLabels = () => wrapper.findAllComponents(GlLabel);
const findRegularLabel = () => findAllLabels().at(1); const findRegularLabel = () => findAllLabels().at(2);
const findScopedLabel = () => findAllLabels().at(0); const findScopedLabel = () => findAllLabels().at(0);
const findLockedLabel = () => findAllLabels().at(1);
const findWrapper = () => wrapper.find('[data-testid="value-wrapper"]'); const findWrapper = () => wrapper.find('[data-testid="value-wrapper"]');
const findEmptyPlaceholder = () => wrapper.find('[data-testid="empty-placeholder"]'); const findEmptyPlaceholder = () => wrapper.find('[data-testid="empty-placeholder"]');
...@@ -18,7 +19,7 @@ describe('DropdownValue', () => { ...@@ -18,7 +19,7 @@ describe('DropdownValue', () => {
wrapper = shallowMount(DropdownValue, { wrapper = shallowMount(DropdownValue, {
slots, slots,
propsData: { propsData: {
selectedLabels: [mockRegularLabel, mockScopedLabel], selectedLabels: [mockLockedLabel, mockRegularLabel, mockScopedLabel],
allowLabelRemove: true, allowLabelRemove: true,
labelsFilterBasePath: '/gitlab-org/my-project/issues', labelsFilterBasePath: '/gitlab-org/my-project/issues',
labelsFilterParam: 'label_name', labelsFilterParam: 'label_name',
...@@ -69,8 +70,8 @@ describe('DropdownValue', () => { ...@@ -69,8 +70,8 @@ describe('DropdownValue', () => {
expect(findEmptyPlaceholder().exists()).toBe(false); expect(findEmptyPlaceholder().exists()).toBe(false);
}); });
it('renders a list of two labels', () => { it('renders a list of three labels', () => {
expect(findAllLabels().length).toBe(2); expect(findAllLabels().length).toBe(3);
}); });
it('passes correct props to the regular label', () => { it('passes correct props to the regular label', () => {
...@@ -96,5 +97,19 @@ describe('DropdownValue', () => { ...@@ -96,5 +97,19 @@ describe('DropdownValue', () => {
wrapper.find('.sidebar-collapsed-icon').trigger('click'); wrapper.find('.sidebar-collapsed-icon').trigger('click');
expect(wrapper.emitted('onCollapsedValueClick')).toEqual([[]]); expect(wrapper.emitted('onCollapsedValueClick')).toEqual([[]]);
}); });
it('does not show close button if label is locked', () => {
createComponent({
supportsLockOnMerge: true,
});
expect(findLockedLabel().props('showCloseButton')).toBe(false);
});
it('shows close button if label is not locked', () => {
createComponent({
supportsLockOnMerge: true,
});
expect(findRegularLabel().props('showCloseButton')).toBe(true);
});
}); });
}); });
import { GlLabel } from '@gitlab/ui'; import { GlLabel } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import EmbeddedLabelsList from '~/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue'; import EmbeddedLabelsList from '~/sidebar/components/labels/labels_select_widget/embedded_labels_list.vue';
import { mockRegularLabel, mockScopedLabel } from './mock_data'; import { mockRegularLabel, mockScopedLabel, mockLockedLabel } from './mock_data';
describe('EmbeddedLabelsList', () => { describe('EmbeddedLabelsList', () => {
let wrapper; let wrapper;
...@@ -13,12 +13,13 @@ describe('EmbeddedLabelsList', () => { ...@@ -13,12 +13,13 @@ describe('EmbeddedLabelsList', () => {
.at(0); .at(0);
const findRegularLabel = () => findLabelByTitle(mockRegularLabel.title); const findRegularLabel = () => findLabelByTitle(mockRegularLabel.title);
const findScopedLabel = () => findLabelByTitle(mockScopedLabel.title); const findScopedLabel = () => findLabelByTitle(mockScopedLabel.title);
const findLockedLabel = () => findLabelByTitle(mockLockedLabel.title);
const createComponent = (props = {}, slots = {}) => { const createComponent = (props = {}, slots = {}) => {
wrapper = shallowMountExtended(EmbeddedLabelsList, { wrapper = shallowMountExtended(EmbeddedLabelsList, {
slots, slots,
propsData: { propsData: {
selectedLabels: [mockRegularLabel, mockScopedLabel], selectedLabels: [mockRegularLabel, mockScopedLabel, mockLockedLabel],
allowLabelRemove: true, allowLabelRemove: true,
labelsFilterBasePath: '/gitlab-org/my-project/issues', labelsFilterBasePath: '/gitlab-org/my-project/issues',
labelsFilterParam: 'label_name', labelsFilterParam: 'label_name',
...@@ -47,8 +48,8 @@ describe('EmbeddedLabelsList', () => { ...@@ -47,8 +48,8 @@ describe('EmbeddedLabelsList', () => {
createComponent(); createComponent();
}); });
it('renders a list of two labels', () => { it('renders a list of three labels', () => {
expect(findAllLabels()).toHaveLength(2); expect(findAllLabels()).toHaveLength(3);
}); });
it('passes correct props to the regular label', () => { it('passes correct props to the regular label', () => {
...@@ -69,5 +70,12 @@ describe('EmbeddedLabelsList', () => { ...@@ -69,5 +70,12 @@ describe('EmbeddedLabelsList', () => {
findRegularLabel().vm.$emit('close'); findRegularLabel().vm.$emit('close');
expect(wrapper.emitted('onLabelRemove')).toStrictEqual([[mockRegularLabel.id]]); expect(wrapper.emitted('onLabelRemove')).toStrictEqual([[mockRegularLabel.id]]);
}); });
it('does not show close button if label is locked', () => {
createComponent({
supportsLockOnMerge: true,
});
expect(findLockedLabel().props('showCloseButton')).toBe(false);
});
}); });
}); });
...@@ -14,6 +14,15 @@ export const mockScopedLabel = { ...@@ -14,6 +14,15 @@ export const mockScopedLabel = {
textColor: '#FFFFFF', textColor: '#FFFFFF',
}; };
export const mockLockedLabel = {
id: 30,
title: 'Bar Label',
description: 'Bar',
color: '#DADA55',
textColor: '#FFFFFF',
lockOnMerge: true,
};
export const mockLabels = [ export const mockLabels = [
mockRegularLabel, mockRegularLabel,
mockScopedLabel, mockScopedLabel,
......
...@@ -17,6 +17,7 @@ const labelsFilterBasePath = '/labels-filter-base-path'; ...@@ -17,6 +17,7 @@ const labelsFilterBasePath = '/labels-filter-base-path';
const initialLabels = []; const initialLabels = [];
const issuableType = 'issue'; const issuableType = 'issue';
const labelType = WORKSPACE_PROJECT; const labelType = WORKSPACE_PROJECT;
const supportsLockOnMerge = false;
const variant = VARIANT_EMBEDDED; const variant = VARIANT_EMBEDDED;
const workspaceType = WORKSPACE_PROJECT; const workspaceType = WORKSPACE_PROJECT;
...@@ -37,6 +38,7 @@ describe('IssuableLabelSelector', () => { ...@@ -37,6 +38,7 @@ describe('IssuableLabelSelector', () => {
initialLabels, initialLabels,
issuableType, issuableType,
labelType, labelType,
supportsLockOnMerge,
variant, variant,
workspaceType, workspaceType,
...injectedProps, ...injectedProps,
......
...@@ -600,7 +600,8 @@ ...@@ -600,7 +600,8 @@
initial_labels: '[]', initial_labels: '[]',
issuable_type: issuable.issuable_type, issuable_type: issuable.issuable_type,
labels_filter_base_path: project_issues_path(project), labels_filter_base_path: project_issues_path(project),
labels_manage_path: project_labels_path(project) labels_manage_path: project_labels_path(project),
supports_lock_on_merge: issuable.supports_lock_on_merge?.to_s
}) })
end end
end end
...@@ -620,7 +621,8 @@ ...@@ -620,7 +621,8 @@
title: label.title, title: label.title,
description: label.description, description: label.description,
color: label.color, color: label.color,
text_color: label.text_color text_color: label.text_color,
lock_on_merge: label.lock_on_merge
}, },
{ {
__typename: "Label", __typename: "Label",
...@@ -628,7 +630,8 @@ ...@@ -628,7 +630,8 @@
title: label2.title, title: label2.title,
description: label2.description, description: label2.description,
color: label2.color, color: label2.color,
text_color: label2.text_color text_color: label2.text_color,
lock_on_merge: label.lock_on_merge
} }
] ]
...@@ -638,7 +641,8 @@ ...@@ -638,7 +641,8 @@
initial_labels: initial_labels.to_json, initial_labels: initial_labels.to_json,
issuable_type: issuable.issuable_type, issuable_type: issuable.issuable_type,
labels_filter_base_path: project_merge_requests_path(project), labels_filter_base_path: project_merge_requests_path(project),
labels_manage_path: project_labels_path(project) labels_manage_path: project_labels_path(project),
supports_lock_on_merge: issuable.supports_lock_on_merge?.to_s
}) })
end end
end end
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
let(:user2) { create(:user) } let(:user2) { create(:user) }
let!(:milestone) { create(:milestone, project: target_project) } let!(:milestone) { create(:milestone, project: target_project) }
let!(:label) { create(:label, project: target_project) } let!(:label) { create(:label, project: target_project) }
let!(:label2) { create(:label, project: target_project) } let!(:label2) { create(:label, project: target_project, lock_on_merge: true) }
let(:target_project) { create(:project, :public, :repository) } let(:target_project) { create(:project, :public, :repository) }
let(:source_project) { target_project } let(:source_project) { target_project }
let(:merge_request) do let(:merge_request) do
......
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
page.within '.labels' do page.within '.labels' do
expect(page).to have_content label.title expect(page).to have_content label.title
expect(page).to have_content label2.title expect(page).to have_content label2.title
expect(page).to have_selector("[data-testid='close-icon']", count: 1)
end end
end end
end end
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册