Commit 4e53ae6c authored by Florie Guibert's avatar Florie Guibert Committed by Natalia Tepluhina

Clean up labels_widget feature flag

parent a9a8dd14
......@@ -3,6 +3,7 @@ import Mousetrap from 'mousetrap';
import { clickCopyToClipboardButton } from '~/behaviors/copy_to_clipboard';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import { isElementVisible } from '~/lib/utils/dom_utils';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import Sidebar from '../../right_sidebar';
import { CopyAsGFM } from '../markdown/copy_as_gfm';
import {
......@@ -114,6 +115,11 @@ export default class ShortcutsIssuable extends Shortcuts {
static openSidebarDropdown(name) {
Sidebar.instance.openDropdown(name);
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
document.querySelector(`.block.${name} .shortcut-sidebar-dropdown-toggle`).click();
}, DEBOUNCE_DROPDOWN_DELAY);
return false;
}
......
......@@ -4,7 +4,6 @@ import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { __, sprintf } from '~/locale';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
......@@ -26,7 +25,6 @@ export default {
SidebarDateWidget,
SidebarConfidentialityWidget,
BoardSidebarTimeTracker,
BoardSidebarLabelsSelect,
SidebarLabelsWidget,
SidebarSubscriptionsWidget,
SidebarDropdownWidget,
......@@ -210,7 +208,6 @@ export default {
data-testid="sidebar-due-date"
/>
<sidebar-labels-widget
v-if="glFeatures.labelsWidget"
class="block labels"
data-testid="sidebar-labels"
:iid="activeBoardItem.iid"
......@@ -230,7 +227,6 @@ export default {
>
{{ __('None') }}
</sidebar-labels-widget>
<board-sidebar-labels-select v-else class="block labels" />
<sidebar-weight-widget
v-if="weightFeatureAvailable"
:iid="activeBoardItem.iid"
......
......@@ -39,7 +39,6 @@ import boardLabelsQuery from '../graphql/board_labels.query.graphql';
import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql';
import groupProjectsQuery from '../graphql/group_projects.query.graphql';
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql';
......@@ -609,33 +608,6 @@ export default {
setActiveIssueLabels: async ({ commit, getters }, input) => {
const { activeBoardItem } = getters;
if (!gon.features?.labelsWidget) {
const { data } = await gqlClient.mutate({
mutation: issueSetLabelsMutation,
variables: {
input: {
iid: input.iid || String(activeBoardItem.iid),
labelIds: input.labelsId ?? undefined,
addLabelIds: input.addLabelIds ?? [],
removeLabelIds: input.removeLabelIds ?? [],
projectPath: input.projectPath,
},
},
});
if (data.updateIssue?.errors?.length > 0) {
throw new Error(data.updateIssue.errors);
}
commit(types.UPDATE_BOARD_ITEM_BY_ID, {
itemId: data.updateIssue?.issue?.id || activeBoardItem.id,
prop: 'labels',
value: data.updateIssue?.issue?.labels.nodes,
});
return;
}
let labels = input?.labels || [];
if (input.removeLabelIds) {
labels = activeBoardItem.labels.filter(
......
......@@ -3,7 +3,6 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
import { hide, fixTitle } from '~/tooltips';
import { DEBOUNCE_DROPDOWN_DELAY } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import createFlash from './flash';
import axios from './lib/utils/axios_utils';
import { sprintf, s__, __ } from './locale';
......@@ -127,14 +126,6 @@ Sidebar.prototype.openDropdown = function (blockOrName) {
this.setCollapseAfterUpdate($block);
this.toggleSidebar('open');
}
// Wait for the sidebar to trigger('click') open
// so it doesn't cause our dropdown to close preemptively
setTimeout(() => {
if (!gon.features?.labelsWidget && !$block.hasClass('labels-select-wrapper')) {
$block.find('.js-sidebar-dropdown-toggle').trigger('click');
}
}, DEBOUNCE_DROPDOWN_DELAY);
};
Sidebar.prototype.setCollapseAfterUpdate = function ($block) {
......
<script>
import $ from 'jquery';
import { camelCase, difference, union } from 'lodash';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import createFlash from '~/flash';
import { getIdFromGraphQLId, MutationOperationMode } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants';
import { __ } from '~/locale';
import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_request_labels.mutation.graphql';
import { toLabelGid } from '~/sidebar/utils';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const mutationMap = {
[IssuableType.Issue]: {
mutation: updateIssueLabelsMutation,
mutationName: 'updateIssue',
},
[IssuableType.MergeRequest]: {
mutation: updateMergeRequestLabelsMutation,
mutationName: 'mergeRequestSetLabels',
},
};
export default {
components: {
LabelsSelect,
LabelsSelectWidget,
},
variant: DropdownVariant.Sidebar,
mixins: [glFeatureFlagMixin()],
inject: [
'allowLabelCreate',
'allowLabelEdit',
'allowScopedLabels',
'iid',
'fullPath',
'initiallySelectedLabels',
'issuableType',
'labelsFetchPath',
'labelsManagePath',
'projectIssuesPath',
'projectPath',
],
inject: ['allowLabelEdit', 'iid', 'fullPath', 'issuableType', 'projectIssuesPath'],
data() {
return {
isLabelsSelectInProgress: false,
selectedLabels: this.initiallySelectedLabels,
LabelType,
};
},
methods: {
handleDropdownClose() {
$(this.$el).trigger('hidden.gl.dropdown');
},
getUpdateVariables(labels) {
let labelIds = [];
if (this.glFeatures.labelsWidget) {
labelIds = labels.map(({ id }) => toLabelGid(id));
} else {
const currentLabelIds = this.selectedLabels.map((label) => label.id);
const userAddedLabelIds = labels.filter((label) => label.set).map((label) => label.id);
const userRemovedLabelIds = labels.filter((label) => !label.set).map((label) => label.id);
labelIds = difference(union(currentLabelIds, userAddedLabelIds), userRemovedLabelIds).map(
toLabelGid,
);
}
switch (this.issuableType) {
case IssuableType.Issue:
return {
iid: this.iid,
projectPath: this.projectPath,
labelIds,
};
case IssuableType.MergeRequest:
return {
iid: this.iid,
labelIds,
operationMode: MutationOperationMode.Replace,
projectPath: this.projectPath,
};
default:
return {};
}
},
handleUpdateSelectedLabels(dropdownLabels) {
this.updateSelectedLabels(this.getUpdateVariables(dropdownLabels));
},
getRemoveVariables(labelId) {
switch (this.issuableType) {
case IssuableType.Issue:
return {
iid: this.iid,
projectPath: this.projectPath,
removeLabelIds: [labelId],
};
case IssuableType.MergeRequest:
return {
iid: this.iid,
labelIds: [toLabelGid(labelId)],
operationMode: MutationOperationMode.Remove,
projectPath: this.projectPath,
};
default:
return {};
}
},
handleLabelRemove(labelId) {
this.updateSelectedLabels(this.getRemoveVariables(labelId));
},
updateSelectedLabels(inputVariables) {
this.isLabelsSelectInProgress = true;
this.$apollo
.mutate({
mutation: mutationMap[this.issuableType].mutation,
variables: { input: inputVariables },
})
.then(({ data }) => {
const { mutationName } = mutationMap[this.issuableType];
if (data[mutationName]?.errors?.length) {
throw new Error();
}
const issuableType = camelCase(this.issuableType);
this.selectedLabels = data[mutationName]?.[issuableType]?.labels?.nodes?.map((label) => ({
...label,
id: getIdFromGraphQLId(label.id),
}));
})
.catch(() => createFlash({ message: __('An error occurred while updating labels.') }))
.finally(() => {
this.isLabelsSelectInProgress = false;
});
},
},
};
</script>
<template>
<labels-select-widget
v-if="glFeatures.labelsWidget"
class="block labels js-labels-block"
:iid="iid"
:full-path="fullPath"
......@@ -165,28 +37,4 @@ export default {
>
{{ __('None') }}
</labels-select-widget>
<labels-select
v-else
class="block labels js-labels-block"
:allow-label-remove="allowLabelEdit"
:allow-label-create="allowLabelCreate"
:allow-label-edit="allowLabelEdit"
:allow-multiselect="true"
:allow-scoped-labels="allowScopedLabels"
:footer-create-label-title="__('Create project label')"
:footer-manage-label-title="__('Manage project labels')"
:labels-create-title="__('Create project label')"
:labels-fetch-path="labelsFetchPath"
:labels-filter-base-path="projectIssuesPath"
:labels-manage-path="labelsManagePath"
:labels-select-in-progress="isLabelsSelectInProgress"
:selected-labels="selectedLabels"
:variant="$options.sidebar"
data-qa-selector="labels_block"
@onDropdownClose="handleDropdownClose"
@onLabelRemove="handleLabelRemove"
@updateSelectedLabels="handleUpdateSelectedLabels"
>
{{ __('None') }}
</labels-select>
</template>
......@@ -231,9 +231,10 @@ export default {
throw new Error();
}
this.issuableLabels = data[mutationName]?.[this.issuableType]?.labels?.nodes;
this.$emit('updateSelectedLabels', {
id: data[mutationName]?.[this.issuableType]?.id,
labels: data[mutationName]?.[this.issuableType]?.labels?.nodes,
labels: this.issuableLabels,
});
})
.catch((error) =>
......
......@@ -11,7 +11,6 @@ class Groups::BoardsController < Groups::ApplicationController
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, group, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.use { }
e.try { }
......
......@@ -11,7 +11,6 @@ class Projects::BoardsController < Projects::ApplicationController
push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.use { }
e.try { }
......
......@@ -51,7 +51,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:real_time_issue_sidebar, @project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
experiment(:invite_members_in_comment, namespace: @project.root_ancestor) do |experiment_instance|
......
......@@ -42,7 +42,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
......
---
name: labels_widget
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62898
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332327
milestone: '14.0'
type: development
group: group::project management
default_enabled: false
import $ from 'jquery';
import Cookies from 'js-cookie';
import Mousetrap from 'mousetrap';
import {
keysFor,
......@@ -8,7 +7,6 @@ import {
ISSUABLE_EDIT_DESCRIPTION,
} from '~/behaviors/shortcuts/keybindings';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { parseBoolean } from '~/lib/utils/common_utils';
export default class ShortcutsEpic extends ShortcutsIssuable {
constructor() {
......@@ -23,15 +21,7 @@ export default class ShortcutsEpic extends ShortcutsIssuable {
Mousetrap.bind(keysFor(ISSUABLE_EDIT_DESCRIPTION), ShortcutsIssuable.editIssue);
}
static openSidebarDropdown($block) {
if (gon.features?.labelsWidget) {
document.dispatchEvent(new Event('toggleSidebarRevealLabelsDropdown'));
return;
}
if (parseBoolean(Cookies.get('collapsed_gutter'))) {
document.dispatchEvent(new Event('toggleSidebarRevealLabelsDropdown'));
} else {
$block.find('.js-sidebar-dropdown-toggle').get(0).dispatchEvent(new Event('click'));
}
static openSidebarDropdown() {
document.dispatchEvent(new Event('toggleSidebarRevealLabelsDropdown'));
}
}
......@@ -3,7 +3,6 @@ import { GlDrawer } from '@gitlab/ui';
import { MountingPortal } from 'portal-vue';
import { mapState, mapActions, mapGetters } from 'vuex';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
......@@ -19,7 +18,6 @@ export default {
components: {
GlDrawer,
SidebarTodoWidget,
BoardSidebarLabelsSelect,
BoardSidebarTitle,
SidebarLabelsWidget,
SidebarConfidentialityWidget,
......@@ -113,7 +111,6 @@ export default {
:can-inherit="true"
/>
<sidebar-labels-widget
v-if="glFeatures.labelsWidget"
class="block labels"
data-testid="sidebar-labels"
:iid="activeBoardItem.iid"
......@@ -130,7 +127,6 @@ export default {
>
{{ __('None') }}
</sidebar-labels-widget>
<board-sidebar-labels-select v-else class="labels" />
<sidebar-confidentiality-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
......
mutation updateBoardEpicLabels($input: UpdateEpicInput!) {
updateEpic(input: $input) {
epic {
id
labels {
nodes {
id
title
color
description
}
}
}
errors
}
}
......@@ -36,7 +36,6 @@ import listUpdateLimitMetricsMutation from '../graphql/list_update_limit_metrics
import listsEpicsQuery from '../graphql/lists_epics.query.graphql';
import subGroupsQuery from '../graphql/sub_groups.query.graphql';
import updateBoardEpicUserPreferencesMutation from '../graphql/update_board_epic_user_preferences.mutation.graphql';
import updateEpicLabelsMutation from '../graphql/update_epic_labels.mutation.graphql';
import * as types from './mutation_types';
......@@ -631,35 +630,9 @@ export default {
}
},
setActiveEpicLabels: async ({ commit, getters, state }, input) => {
setActiveEpicLabels: async ({ commit, getters }, input) => {
const { activeBoardItem } = getters;
if (!gon.features?.labelsWidget) {
const { data } = await gqlClient.mutate({
mutation: updateEpicLabelsMutation,
variables: {
input: {
iid: String(activeBoardItem.iid),
addLabelIds: input.addLabelIds ?? [],
removeLabelIds: input.removeLabelIds ?? [],
groupPath: state.fullPath,
},
},
});
if (data.updateEpic?.errors?.length > 0) {
throw new Error(data.updateEpic.errors);
}
commit(typesCE.UPDATE_BOARD_ITEM_BY_ID, {
itemId: activeBoardItem.id,
prop: 'labels',
value: data.updateEpic.epic.labels.nodes,
});
return;
}
let labels = input?.labels || [];
if (input.removeLabelIds) {
labels = activeBoardItem.labels.filter(
......
......@@ -13,10 +13,8 @@ import { formatDate } from '~/lib/utils/datetime_utility';
import { visitUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import LabelsSelectVue from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import createEpic from '../queries/createEpic.mutation.graphql';
export default {
......@@ -28,18 +26,9 @@ export default {
GlFormInput,
GlFormGroup,
MarkdownField,
LabelsSelectVue,
LabelsSelectWidget,
},
mixins: [glFeatureFlagMixin()],
inject: [
'groupPath',
'groupEpicsPath',
'labelsFetchPath',
'labelsManagePath',
'markdownPreviewPath',
'markdownDocsPath',
],
inject: ['groupPath', 'groupEpicsPath', 'markdownPreviewPath', 'markdownDocsPath'],
data() {
return {
title: '',
......@@ -113,20 +102,7 @@ export default {
this.startDateFixed = val;
},
handleUpdateSelectedLabels(labels) {
if (this.glFeatures.labelsWidget) {
this.labels = labels.map((label) => ({ ...label, id: getIdFromGraphQLId(label.id) }));
return;
}
const ids = [];
const allLabels = [...labels, ...this.labels];
this.labels = allLabels.filter((label) => {
const exists = ids.includes(label.id);
ids.push(label.id);
return !exists && label.set;
});
this.labels = labels.map((label) => ({ ...label, id: getIdFromGraphQLId(label.id) }));
},
},
};
......@@ -190,7 +166,6 @@ export default {
<hr />
<gl-form-group :label="__('Labels')">
<labels-select-widget
v-if="glFeatures.labelsWidget"
class="block labels js-labels-block"
:full-path="groupPath"
:allow-label-create="true"
......@@ -207,24 +182,6 @@ export default {
>
{{ __('None') }}
</labels-select-widget>
<labels-select-vue
v-else
:allow-label-edit="false"
:allow-label-create="true"
:allow-multiselect="true"
:allow-scoped-labels="false"
:selected-labels="labels"
:labels-fetch-path="labelsFetchPath"
:labels-manage-path="labelsManagePath"
:labels-filter-base-path="groupEpicsPath"
:labels-list-title="__('Select label')"
:dropdown-button-text="__('Choose labels')"
variant="embedded"
class="block labels js-labels-block"
@updateSelectedLabels="handleUpdateSelectedLabels"
>
{{ __('None') }}
</labels-select-vue>
</gl-form-group>
<gl-form-group :label="__('Start date')" :description="$options.i18n.epicDatesHint">
<div class="gl-display-inline-block gl-mr-2">
......
......@@ -22,7 +22,6 @@ import { dateTypes } from '../constants';
import epicUtils from '../utils/epic_utils';
import SidebarDatePicker from './sidebar_items/sidebar_date_picker.vue';
import SidebarHeader from './sidebar_items/sidebar_header.vue';
import SidebarLabels from './sidebar_items/sidebar_labels.vue';
export default {
dateTypes,
......@@ -30,7 +29,6 @@ export default {
SidebarHeader,
SidebarDatePicker,
SidebarDatePickerCollapsed,
SidebarLabels,
SidebarAncestorsWidget,
SidebarParticipantsWidget,
SidebarConfidentialityWidget,
......@@ -239,7 +237,6 @@ export default {
@toggleCollapse="toggleSidebar({ sidebarCollapsed })"
/>
<labels-select-widget
v-if="glFeatures.labelsWidget"
class="block labels js-labels-block"
:iid="String(iid)"
:full-path="fullPath"
......@@ -256,12 +253,6 @@ export default {
>
{{ __('None') }}
</labels-select-widget>
<sidebar-labels
v-else
:can-update="canUpdate"
:sidebar-collapsed="sidebarCollapsed"
data-testid="labels-select"
/>
<sidebar-confidentiality-widget
:iid="String(iid)"
:full-path="fullPath"
......
......@@ -8,9 +8,6 @@ class Groups::EpicBoardsController < Groups::ApplicationController
before_action :redirect_to_recent_board, only: [:index]
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:labels_widget, group, default_enabled: :yaml)
end
track_redis_hll_event :index, :show, name: 'g_project_management_users_viewing_epic_boards'
......
......@@ -23,7 +23,6 @@ class Groups::EpicsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:vue_epics_list, @group, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:improved_emoji_picker, @group, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, @group, default_enabled: :yaml)
end
feature_category :portfolio_management
......
......@@ -69,9 +69,29 @@ exports[`ee/BoardContentSidebar matches the snapshot 1`] = `
issuabletype="issue"
/>
<boardsidebarlabelsselect-stub
<sidebarlabelswidget-stub
allowmultiselect="true"
attrworkspacepath="gitlab-org/gitlab-test"
class="block labels"
/>
data-testid="sidebar-labels"
dropdownbuttontext="Label"
footercreatelabeltitle="Create project label"
footermanagelabeltitle="Manage project labels"
fullpath="gitlab-org/gitlab-test"
iid="27"
issuabletype="issue"
labelcreatetype="project"
labelscreatetitle="Create project label"
labelsfilterbasepath=""
labelsfilterparam="label_name"
labelslisttitle="Assign labels"
variant="sidebar"
workspacetype="project"
>
None
</sidebarlabelswidget-stub>
<sidebarweightwidget-stub
full-path="gitlab-org/gitlab-test"
......
import { GlDrawer } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { stubComponent } from 'helpers/stub_component';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import { ISSUABLE, issuableTypes } from '~/boards/constants';
import { mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data';
Vue.use(Vuex);
describe('ee/BoardContentSidebar', () => {
let wrapper;
let store;
......@@ -25,6 +28,7 @@ describe('ee/BoardContentSidebar', () => {
projectPathForActiveIssue: () => mockIssueProjectPath,
groupPathForActiveIssue: () => mockIssueGroupPath,
isSidebarOpen: () => true,
isGroupBoard: () => false,
...mockGetters,
},
actions: mockActions,
......@@ -69,7 +73,7 @@ describe('ee/BoardContentSidebar', () => {
BoardEditableItem: true,
BoardSidebarTitle: true,
BoardSidebarTimeTracker: true,
BoardSidebarLabelsSelect: true,
SidebarLabelsWidget: true,
SidebarAssigneesWidget: true,
SidebarConfidentialityWidget: true,
SidebarDateWidget: true,
......
import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vue from 'vue';
import Vuex from 'vuex';
import EpicBoardContentSidebar from 'ee_component/boards/components/epic_board_content_sidebar.vue';
import SidebarAncestorsWidget from 'ee_component/sidebar/components/ancestors_tree/sidebar_ancestors_widget.vue';
import { stubComponent } from 'helpers/stub_component';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
......@@ -13,8 +13,11 @@ import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { mockFormattedBoardEpic } from '../mock_data';
Vue.use(Vuex);
describe('EpicBoardContentSidebar', () => {
let wrapper;
let store;
......@@ -95,8 +98,8 @@ describe('EpicBoardContentSidebar', () => {
);
});
it('renders BoardSidebarLabelsSelect', () => {
expect(wrapper.findComponent(BoardSidebarLabelsSelect).exists()).toBe(true);
it('renders LabelsSelectWidget', () => {
expect(wrapper.findComponent(LabelsSelectWidget).exists()).toBe(true);
});
it('renders BoardSidebarTitle', () => {
......
......@@ -1340,7 +1340,7 @@ describe('setSelectedGroup', () => {
describe('setActiveEpicLabels', () => {
const state = { boardItems: { [mockEpic.id]: mockEpic } };
const getters = { activeBoardItem: mockEpic };
const getters = { activeBoardItem: { ...mockEpic, labels } };
const testLabelIds = labels.map((label) => label.id);
const input = {
addLabelIds: testLabelIds,
......@@ -1348,11 +1348,7 @@ describe('setActiveEpicLabels', () => {
groupPath: 'h/b',
};
it('should assign labels on success', (done) => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateEpic: { epic: { labels: { nodes: labels } } } } });
it('should assign labels', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
......@@ -1365,78 +1361,32 @@ describe('setActiveEpicLabels', () => {
{ ...state, ...getters },
[
{
type: typesCE.UPDATE_BOARD_ITEM_BY_ID,
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
done,
);
});
it('throws error if fails', async () => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateEpic: { errors: ['failed mutation'] } } });
await expect(actions.setActiveEpicLabels({ getters }, input)).rejects.toThrow(Error);
});
describe('labels_widget FF on', () => {
beforeEach(() => {
window.gon = {
features: { labelsWidget: true },
};
getters.activeBoardItem = { ...mockIssue, labels };
});
afterEach(() => {
window.gon = {
features: {},
};
});
it('should assign labels', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: labels,
};
testAction(
actions.setActiveEpicLabels,
input,
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
it('should remove label', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: [labels[1]],
};
it('should remove label', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: [labels[1]],
};
testAction(
actions.setActiveEpicLabels,
{ ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
testAction(
actions.setActiveEpicLabels,
{ ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
});
......@@ -6,7 +6,7 @@ import EpicForm from 'ee/epic/components/epic_form.vue';
import createEpic from 'ee/epic/queries/createEpic.mutation.graphql';
import { TEST_HOST } from 'helpers/test_constants';
import { visitUrl } from '~/lib/utils/url_utility';
import LabelsSelectVue from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
......@@ -48,7 +48,7 @@ describe('ee/epic/components/epic_form.vue', () => {
});
const findForm = () => wrapper.find(GlForm);
const findLabels = () => wrapper.find(LabelsSelectVue);
const findLabels = () => wrapper.find(LabelsSelectWidget);
const findTitle = () => wrapper.find('[data-testid="epic-title"]');
const findDescription = () => wrapper.find('[data-testid="epic-description"]');
const findConfidentialityCheck = () => wrapper.find('[data-testid="epic-confidentiality"]');
......@@ -113,7 +113,9 @@ describe('ee/epic/components/epic_form.vue', () => {
findTitle().vm.$emit('input', title);
findDescription().setValue(description);
findConfidentialityCheck().vm.$emit('input', confidential);
findLabels().vm.$emit('updateSelectedLabels', [{ id: 1, set: 1 }]);
findLabels().vm.$emit('updateSelectedLabels', {
labels: [{ id: 'gid://gitlab/GroupLabel/1' }],
});
// Make sure the submitted values for start and due dates are date strings without timezone info.
// (Datepicker emits a Date object but the submitted value must be a date string).
......
......@@ -124,15 +124,6 @@ module QA
click_element(:more_assignees_link)
end
# When the labels_widget feature flag is enabled, wait until the labels widget appears
def wait_for_labels_widget_feature_flag
Support::Retrier.retry_until(max_duration: 60, reload_page: page, retry_on_exception: true, sleep_interval: 5) do
within_element(:labels_block) do
find_element(:edit_link)
end
end
end
private
def wait_assignees_block_finish_loading
......
......@@ -19,8 +19,6 @@ module QA
end
before do
Runtime::Feature.enable(:labels_widget, project: issue.project)
Flow::Login.sign_in
[
......@@ -43,9 +41,6 @@ module QA
testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/quality/test_cases/1181'
) do
Page::Project::Issue::Show.perform do |show|
# TODO: Remove this method when the `Runtime::Feature.enable` method call is removed
show.wait_for_labels_widget_feature_flag
show.select_labels(
[
new_label_same_scope,
......
......@@ -15,7 +15,6 @@ RSpec.describe "Issues > User edits issue", :js do
context 'with authorized user' do
before do
stub_feature_flags(labels_widget: false)
project.add_developer(user)
project_with_milestones.add_developer(user)
sign_in(user)
......@@ -151,7 +150,7 @@ RSpec.describe "Issues > User edits issue", :js do
page.within '.block.labels' do
# Remove `verisimilitude` label
within '.gl-label' do
click_button
click_button 'Remove label'
end
expect(page).to have_text('syzygy')
......
......@@ -17,7 +17,6 @@ RSpec.describe 'Labels Hierarchy', :js do
let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') }
before do
stub_feature_flags(labels_widget: false)
grandparent.add_owner(user)
sign_in(user)
......@@ -28,13 +27,12 @@ RSpec.describe 'Labels Hierarchy', :js do
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
page.within('.block.labels') do
click_on 'Edit'
end
wait_for_requests
wait_for_requests
find('a.label-item', text: label.title).click
wait_for_requests
click_on 'Close'
click_on label.title
click_on 'Close'
end
wait_for_requests
......
import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
import Vue from 'vue';
import Vuex from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { stubComponent } from 'helpers/stub_component';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
import SidebarLabelsWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { mockActiveIssue, mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data';
Vue.use(Vuex);
describe('BoardContentSidebar', () => {
let wrapper;
let store;
......@@ -32,6 +34,7 @@ describe('BoardContentSidebar', () => {
groupPathForActiveIssue: () => mockIssueGroupPath,
projectPathForActiveIssue: () => mockIssueProjectPath,
isSidebarOpen: () => true,
isGroupBoard: () => false,
...mockGetters,
},
actions: mockActions,
......@@ -115,8 +118,8 @@ describe('BoardContentSidebar', () => {
expect(wrapper.findComponent(SidebarTodoWidget).exists()).toBe(true);
});
it('renders BoardSidebarLabelsSelect', () => {
expect(wrapper.findComponent(BoardSidebarLabelsSelect).exists()).toBe(true);
it('renders SidebarLabelsWidget', () => {
expect(wrapper.findComponent(SidebarLabelsWidget).exists()).toBe(true);
});
it('renders BoardSidebarTitle', () => {
......
......@@ -1570,7 +1570,7 @@ describe('addListNewIssue', () => {
describe('setActiveIssueLabels', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
const getters = { activeBoardItem: mockIssue };
const getters = { activeBoardItem: { ...mockIssue, labels } };
const testLabelIds = labels.map((label) => label.id);
const input = {
labelIds: testLabelIds,
......@@ -1579,11 +1579,7 @@ describe('setActiveIssueLabels', () => {
labels,
};
it('should assign labels on success', (done) => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } });
it('should assign labels', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
......@@ -1601,74 +1597,28 @@ describe('setActiveIssueLabels', () => {
},
],
[],
done,
);
});
it('throws error if fails', async () => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
await expect(actions.setActiveIssueLabels({ getters }, input)).rejects.toThrow(Error);
});
describe('labels_widget FF on', () => {
beforeEach(() => {
window.gon = {
features: { labelsWidget: true },
};
getters.activeBoardItem = { ...mockIssue, labels };
});
afterEach(() => {
window.gon = {
features: {},
};
});
it('should assign labels', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: labels,
};
testAction(
actions.setActiveIssueLabels,
input,
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
it('should remove label', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: [labels[1]],
};
it('should remove label', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
value: [labels[1]],
};
testAction(
actions.setActiveIssueLabels,
{ ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
testAction(
actions.setActiveIssueLabels,
{ ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
{ ...state, ...getters },
[
{
type: types.UPDATE_BOARD_ITEM_BY_ID,
payload,
},
],
[],
);
});
});
......
import { shallowMount } from '@vue/test-utils';
import {
mockLabels,
mockRegularLabel,
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import { MutationOperationMode } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants';
import SidebarLabels from '~/sidebar/components/labels/sidebar_labels.vue';
import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_request_labels.mutation.graphql';
import { toLabelGid } from '~/sidebar/utils';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
import {
DropdownVariant,
LabelType,
} from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
describe('sidebar labels', () => {
let wrapper;
const defaultProps = {
allowLabelCreate: true,
allowLabelEdit: true,
allowScopedLabels: true,
canEdit: true,
iid: '1',
initiallySelectedLabels: mockLabels,
issuableType: 'issue',
labelsFetchPath: '/gitlab-org/gitlab-test/-/labels.json?include_ancestor_groups=true',
labelsManagePath: '/gitlab-org/gitlab-test/-/labels',
projectIssuesPath: '/gitlab-org/gitlab-test/-/issues',
projectPath: 'gitlab-org/gitlab-test',
fullPath: 'gitlab-org/gitlab-test',
};
const $apollo = {
mutate: jest.fn().mockResolvedValue(),
};
const userUpdatedLabels = [
{
...mockRegularLabel,
set: false,
},
{
id: 40,
title: 'Security',
color: '#ddd',
text_color: '#fff',
set: true,
},
{
id: 55,
title: 'Tooling',
color: '#ddd',
text_color: '#fff',
set: false,
},
];
const findLabelsSelect = () => wrapper.find(LabelsSelect);
const mountComponent = (props = {}) => {
......@@ -63,9 +25,6 @@ describe('sidebar labels', () => {
...defaultProps,
...props,
},
mocks: {
$apollo,
},
});
};
......@@ -75,115 +34,31 @@ describe('sidebar labels', () => {
});
describe('LabelsSelect props', () => {
beforeEach(() => {
mountComponent();
});
it('are as expected', () => {
expect(findLabelsSelect().props()).toMatchObject({
allowLabelCreate: defaultProps.allowLabelCreate,
allowLabelEdit: defaultProps.allowLabelEdit,
allowMultiselect: true,
allowScopedLabels: defaultProps.allowScopedLabels,
footerCreateLabelTitle: 'Create project label',
footerManageLabelTitle: 'Manage project labels',
labelsCreateTitle: 'Create project label',
labelsFetchPath: defaultProps.labelsFetchPath,
labelsFilterBasePath: defaultProps.projectIssuesPath,
labelsManagePath: defaultProps.labelsManagePath,
labelsSelectInProgress: false,
selectedLabels: defaultProps.initiallySelectedLabels,
variant: DropdownVariant.Sidebar,
});
});
});
describe('when type is issue', () => {
beforeEach(() => {
mountComponent({ issuableType: IssuableType.Issue });
});
describe('when labels are updated', () => {
it('invokes a mutation', () => {
findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
const expected = {
mutation: updateIssueLabelsMutation,
variables: {
input: {
iid: defaultProps.iid,
projectPath: defaultProps.projectPath,
labelIds: [toLabelGid(29), toLabelGid(28), toLabelGid(27), toLabelGid(40)],
},
},
};
expect($apollo.mutate).toHaveBeenCalledWith(expected);
describe.each`
issuableType
${'issue'}
${'merge_request'}
`('issuableType $issuableType', ({ issuableType }) => {
beforeEach(() => {
mountComponent({ issuableType });
});
});
describe('when label `x` is clicked', () => {
it('invokes a mutation', () => {
findLabelsSelect().vm.$emit('onLabelRemove', 27);
const expected = {
mutation: updateIssueLabelsMutation,
variables: {
input: {
iid: defaultProps.iid,
projectPath: defaultProps.projectPath,
removeLabelIds: [27],
},
},
};
expect($apollo.mutate).toHaveBeenCalledWith(expected);
});
});
});
describe('when type is merge_request', () => {
beforeEach(() => {
mountComponent({ issuableType: IssuableType.MergeRequest });
});
describe('when labels are updated', () => {
it('invokes a mutation', () => {
findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
const expected = {
mutation: updateMergeRequestLabelsMutation,
variables: {
input: {
iid: defaultProps.iid,
labelIds: [toLabelGid(29), toLabelGid(28), toLabelGid(27), toLabelGid(40)],
operationMode: MutationOperationMode.Replace,
projectPath: defaultProps.projectPath,
},
},
};
expect($apollo.mutate).toHaveBeenCalledWith(expected);
});
});
describe('when label `x` is clicked', () => {
it('invokes a mutation', () => {
findLabelsSelect().vm.$emit('onLabelRemove', 27);
const expected = {
mutation: updateMergeRequestLabelsMutation,
variables: {
input: {
iid: defaultProps.iid,
labelIds: [toLabelGid(27)],
operationMode: MutationOperationMode.Remove,
projectPath: defaultProps.projectPath,
},
},
};
expect($apollo.mutate).toHaveBeenCalledWith(expected);
it('has expected props', () => {
expect(findLabelsSelect().props()).toMatchObject({
iid: defaultProps.iid,
fullPath: defaultProps.fullPath,
allowLabelRemove: defaultProps.allowLabelEdit,
allowMultiselect: true,
footerCreateLabelTitle: 'Create project label',
footerManageLabelTitle: 'Manage project labels',
labelsCreateTitle: 'Create project label',
labelsFilterBasePath: defaultProps.projectIssuesPath,
variant: DropdownVariant.Sidebar,
issuableType,
workspaceType: 'project',
attrWorkspacePath: defaultProps.fullPath,
labelCreateType: LabelType.project,
});
});
});
});
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
......@@ -8,14 +8,14 @@ import { IssuableType } from '~/issue_show/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue';
import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue';
import issueLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql';
import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { mockConfig, issuableLabelsQueryResponse } from './mock_data';
jest.mock('~/flash');
const localVue = createLocalVue();
localVue.use(VueApollo);
Vue.use(VueApollo);
const successfulQueryHandler = jest.fn().mockResolvedValue(issuableLabelsQueryResponse);
const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
......@@ -25,6 +25,7 @@ describe('LabelsSelectRoot', () => {
const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findDropdownValue = () => wrapper.findComponent(DropdownValue);
const findDropdownValueCollapsed = () => wrapper.findComponent(DropdownValueCollapsed);
const findDropdownContents = () => wrapper.findComponent(DropdownContents);
const createComponent = ({
......@@ -37,7 +38,6 @@ describe('LabelsSelectRoot', () => {
wrapper = shallowMount(LabelsSelectRoot, {
slots,
apolloProvider: mockApollo,
localVue,
propsData: {
...config,
issuableType: IssuableType.Issue,
......@@ -107,6 +107,9 @@ describe('LabelsSelectRoot', () => {
expect(findDropdownValue().props('selectedLabels')).toEqual(
issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
);
expect(findDropdownValueCollapsed().props('labels')).toEqual(
issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
);
});
it('emits `onLabelRemove` event on dropdown value label remove event', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment