Commit 6450fd5d authored by Nathan Friend's avatar Nathan Friend

Merge branch '330479-graphql-issue-board-uses-a-wrong-labels-endpoint' into 'master'

Correctly fetch labels in GraphQL boards sidebar [RUN-AS-IF-FOSS]

See merge request gitlab-org/gitlab!61423
parents 6db586a8 bdd3c8d2
<script>
import { GlLabel } from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
import Api from '~/api';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
......@@ -14,7 +16,13 @@ export default {
LabelsSelect,
GlLabel,
},
inject: ['labelsFetchPath', 'labelsManagePath', 'labelsFilterBasePath'],
inject: {
labelsFetchPath: {
default: null,
},
labelsManagePath: {},
labelsFilterBasePath: {},
},
data() {
return {
loading: false,
......@@ -38,6 +46,32 @@ export default {
scoped: isScopedLabel(label),
}));
},
fetchPath() {
/*
Labels fetched in epic boards are always group-level labels
and the correct path are passed from the backend (injected through labelsFetchPath)
For issue boards, we should always include project-level labels and use a different endpoint.
(it requires knowing the project path of a selected issue.)
Note 1. that we will be using GraphQL to fetch labels when we create a labels select widget.
And this component will be removed _wholesale_ https://gitlab.com/gitlab-org/gitlab/-/issues/300653.
Note 2. Moreover, 'fetchPath' needs to be used as a key for 'labels-select' component to force updates.
'labels-select' has its own vuex store and initializes the passed props as states
and these states aren't reactively bound to the passed props.
*/
const projectLabelsFetchPath = mergeUrlParams(
{ include_ancestor_groups: true },
Api.buildUrl(Api.projectLabelsPath).replace(
':namespace_path/:project_path',
this.projectPathForActiveIssue,
),
);
return this.labelsFetchPath || projectLabelsFetchPath;
},
},
methods: {
...mapActions(['setActiveBoardItemLabels']),
......@@ -100,12 +134,13 @@ export default {
<template #default="{ edit }">
<labels-select
ref="labelsSelect"
:key="fetchPath"
:allow-label-edit="false"
:allow-label-create="false"
:allow-multiselect="true"
:allow-scoped-labels="true"
:selected-labels="selectedLabels"
:labels-fetch-path="labelsFetchPath"
:labels-fetch-path="fetchPath"
:labels-manage-path="labelsManagePath"
:labels-filter-base-path="labelsFilterBasePath"
:labels-list-title="__('Select label')"
......
......@@ -97,7 +97,6 @@ export default () => {
currentUserId: gon.current_user_id || null,
canUpdate: parseBoolean($boardApp.dataset.canUpdate),
canAdminList: parseBoolean($boardApp.dataset.canAdminList),
labelsFetchPath: $boardApp.dataset.labelsFetchPath,
labelsManagePath: $boardApp.dataset.labelsManagePath,
labelsFilterBasePath: $boardApp.dataset.labelsFilterBasePath,
timeTrackingLimitToHours: parseBoolean($boardApp.dataset.timeTrackingLimitToHours),
......
---
title: Display labels from sub groups and projects when using epics swimlanes
merge_request: 61423
author:
type: fixed
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Issue boards sidebar labels using epic swimlanes', :js do
include BoardHelpers
include_context 'labels from nested groups and projects'
before do
stub_licensed_features(epics: true, swimlanes: true)
end
let(:card) { find("[data-testid='board-lane-unassigned-issues']").first("[data-testid='board_card']") }
context 'group boards' do
context 'in the top-level group board' do
let_it_be(:group_board) { create(:board, group: group) }
let_it_be(:board_list) { create(:backlog_list, board: group_board) }
before do
load_board group_board_path(group, group_board)
load_epic_swimlanes
end
context 'selecting an issue from a direct descendant project' do
let_it_be(:project_issue) { create(:issue, project: project) }
include_examples 'an issue from a direct descendant project is selected'
end
context "selecting an issue from a subgroup's project" do
let_it_be(:subproject_issue) { create(:issue, project: subproject) }
include_examples "an issue from a subgroup's project is selected"
end
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'epics swimlanes sidebar', :js do
include BoardHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project, reload: true) { create(:project, :public, group: group) }
......@@ -48,15 +50,6 @@ RSpec.describe 'epics swimlanes sidebar', :js do
it_behaves_like 'issue boards sidebar EE'
end
def load_epic_swimlanes
page.within('.board-swimlanes-toggle-wrapper') do
page.find('.dropdown-toggle').click
page.find('.dropdown-item', text: 'Epic').click
end
wait_for_requests
end
def first_card
find("[data-testid='board-lane-unassigned-issues']").first("[data-testid='board_card']")
end
......
# frozen_string_literal: true
module BoardHelpers
def load_epic_swimlanes
page.within('.board-swimlanes-toggle-wrapper') do
page.find('.dropdown-toggle').click
page.find('.dropdown-item', text: 'Epic').click
end
wait_for_requests
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Issue boards sidebar labels select', :js do
include BoardHelpers
include_context 'labels from nested groups and projects'
let(:card) { find('.board:nth-child(1)').first('[data-testid="board_card"]') }
context 'group boards' do
context 'in the top-level group board' do
let_it_be(:group_board) { create(:board, group: group) }
let_it_be(:board_list) { create(:backlog_list, board: group_board) }
before do
load_board group_board_path(group, group_board)
end
context 'selecting an issue from a direct descendant project' do
let_it_be(:project_issue) { create(:issue, project: project) }
include_examples 'an issue from a direct descendant project is selected'
end
context "selecting an issue from a subgroup's project" do
let_it_be(:subproject_issue) { create(:issue, project: subproject) }
include_examples "an issue from a subgroup's project is selected"
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Sub-group project issue boards', :js do
let(:group) { create(:group) }
let(:nested_group_1) { create(:group, parent: group) }
let(:project) { create(:project, group: nested_group_1) }
let(:board) { create(:board, project: project) }
let(:label) { create(:label, project: project) }
let(:user) { create(:user) }
let!(:list1) { create(:list, board: board, label: label, position: 0) }
let!(:issue) { create(:labeled_issue, project: project, labels: [label]) }
before do
project.add_maintainer(user)
sign_in(user)
visit project_board_path(project, board)
wait_for_requests
end
# TODO https://gitlab.com/gitlab-org/gitlab/-/issues/324290
xit 'creates new label from sidebar' do
find('.board-card').click
page.within '.labels' do
click_link 'Edit'
click_link 'Create project label'
end
page.within '.dropdown-new-label' do
fill_in 'new_label_name', with: 'test label'
first('.suggest-colors-dropdown a').click
click_button 'Create'
wait_for_requests
end
page.within '.labels' do
expect(page).to have_link 'test label'
end
end
end
import { GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { labels as TEST_LABELS, mockIssue as TEST_ISSUE } from 'jest/boards/mock_data';
import {
labels as TEST_LABELS,
mockIssue as TEST_ISSUE,
mockIssueFullPath as TEST_ISSUE_FULLPATH,
} from 'jest/boards/mock_data';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import { createStore } from '~/boards/stores';
......@@ -23,7 +27,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
wrapper = null;
});
const createWrapper = ({ labels = [] } = {}) => {
const createWrapper = ({ labels = [], providedValues = {} } = {}) => {
store = createStore();
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, labels } };
store.state.activeId = TEST_ISSUE.id;
......@@ -32,9 +36,9 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
store,
provide: {
canUpdate: true,
labelsFetchPath: TEST_HOST,
labelsManagePath: TEST_HOST,
labelsFilterBasePath: TEST_HOST,
...providedValues,
},
stubs: {
BoardEditableItem,
......@@ -48,6 +52,22 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
wrapper.findAll(GlLabel).wrappers.map((item) => item.props('title'));
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
describe('when labelsFetchPath is provided', () => {
it('uses injected labels fetch path', () => {
createWrapper({ providedValues: { labelsFetchPath: 'foobar' } });
expect(findLabelsSelect().props('labelsFetchPath')).toEqual('foobar');
});
});
it('uses the default project label endpoint', () => {
createWrapper();
expect(findLabelsSelect().props('labelsFetchPath')).toEqual(
`/${TEST_ISSUE_FULLPATH}/-/labels?include_ancestor_groups=true`,
);
});
it('renders "None" when no labels are selected', () => {
createWrapper();
......@@ -78,7 +98,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
it('commits change to the server', () => {
expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: TEST_LABELS.map((label) => label.id),
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
projectPath: TEST_ISSUE_FULLPATH,
removeLabelIds: [],
});
});
......@@ -103,7 +123,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
addLabelIds: [5, 7],
removeLabelIds: [6],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
projectPath: TEST_ISSUE_FULLPATH,
});
});
});
......@@ -122,7 +142,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
expect(wrapper.vm.setActiveBoardItemLabels).toHaveBeenCalledWith({
removeLabelIds: [getIdFromGraphQLId(testLabel.id)],
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
projectPath: TEST_ISSUE_FULLPATH,
});
});
});
......
......@@ -151,6 +151,8 @@ export const rawIssue = {
},
};
export const mockIssueFullPath = 'gitlab-org/test-subgroup/gitlab-test';
export const mockIssue = {
id: 'gid://gitlab/Issue/436',
iid: '27',
......@@ -159,8 +161,8 @@ export const mockIssue = {
timeEstimate: 0,
weight: null,
confidential: false,
referencePath: 'gitlab-org/test-subgroup/gitlab-test#27',
path: '/gitlab-org/test-subgroup/gitlab-test/-/issues/27',
referencePath: `${mockIssueFullPath}#27`,
path: `/${mockIssueFullPath}/-/issues/27`,
assignees,
labels: [
{
......
......@@ -7,4 +7,20 @@ module BoardHelpers
wait_for_requests
end
end
def load_board(board_path)
visit board_path
wait_for_requests
end
def click_card_and_edit_label
click_card(card)
page.within(labels_select) do
click_button 'Edit'
wait_for_requests
end
end
end
# frozen_string_literal: true
RSpec.shared_context 'labels from nested groups and projects' do
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, group: group, name: 'Group label') }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project_label) { create(:label, project: project, name: 'Project label') }
let_it_be(:subgroup) { create(:group, parent: group) }
let_it_be(:subgroup_label) { create(:group_label, group: subgroup, name: 'Subgroup label') }
let_it_be(:subproject) { create(:project, group: subgroup) }
let_it_be(:subproject_label) { create(:label, project: subproject, name: 'Subproject label') }
let_it_be(:subgroup2) { create(:group, parent: group) }
let_it_be(:subgroup2_label) { create(:group_label, group: subgroup2, name: 'Subgroup2 label') }
let_it_be(:maintainer) { create(:user) }
let(:labels_select) { find("[data-testid='sidebar-labels']") }
let(:labels_dropdown) { labels_select.find('[data-testid="dropdown-content"]')}
before do
group.add_maintainer(maintainer)
sign_in(maintainer)
end
end
RSpec.shared_examples "an issue from a subgroup's project is selected" do
context 'when editing labels' do
before do
click_card_and_edit_label
end
it 'displays the label from the top-level group' do
expect(labels_dropdown).to have_content(group_label.name)
end
it 'displays the label from the subgroup' do
expect(labels_dropdown).to have_content(subgroup_label.name)
end
it 'displays the label from the project' do
expect(labels_dropdown).to have_content(subproject_label.name)
end
it "does not display labels from the subgroup's siblings (project or group)" do
aggregate_failures do
expect(labels_dropdown).not_to have_content(project_label.name)
expect(labels_dropdown).not_to have_content(subgroup2_label.name)
end
end
end
end
RSpec.shared_examples 'an issue from a direct descendant project is selected' do
context 'when editing labels' do
before do
click_card_and_edit_label
end
it 'displays the label from the top-level group' do
expect(labels_dropdown).to have_content(group_label.name)
end
it 'displays the label from the project' do
expect(labels_dropdown).to have_content(project_label.name)
end
it "does not display labels from the project's siblings or their descendents" do
aggregate_failures do
expect(labels_dropdown).not_to have_content(subgroup_label.name)
expect(labels_dropdown).not_to have_content(subproject_label.name)
end
end
end
end
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