Commit fa33a6a4 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'configure-hidden-lists-unlicensed-ee' into 'master'

Fix updating of boards for unlicensed EE

See merge request gitlab-org/gitlab!51561
parents b6780525 6f0577d6
export default () => {}; import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import ConfigToggle from './components/config_toggle.vue';
export default (boardsStore) => {
const el = document.querySelector('.js-board-config');
if (!el) {
return;
}
gl.boardConfigToggle = new Vue({
el,
render(h) {
return h(ConfigToggle, {
props: {
boardsStore,
canAdminList: parseBoolean(el.dataset.canAdminList),
hasScope: parseBoolean(el.dataset.hasScope),
},
});
},
});
};
...@@ -6,7 +6,6 @@ import 'ee_else_ce/boards/models/issue'; ...@@ -6,7 +6,6 @@ import 'ee_else_ce/boards/models/issue';
import 'ee_else_ce/boards/models/list'; import 'ee_else_ce/boards/models/list';
import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar'; import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown'; import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import { import {
setWeightFetchingState, setWeightFetchingState,
setEpicFetchingState, setEpicFetchingState,
...@@ -40,6 +39,7 @@ import { ...@@ -40,6 +39,7 @@ import {
} from '~/lib/utils/common_utils'; } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub'; import sidebarEventHub from '~/sidebar/event_hub';
import boardConfigToggle from './config_toggle';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher'; import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo); Vue.use(VueApollo);
......
...@@ -24,6 +24,7 @@ module Types ...@@ -24,6 +24,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Toggle mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Boards::Create mount_mutation Mutations::Boards::Create
mount_mutation Mutations::Boards::Destroy mount_mutation Mutations::Boards::Destroy
mount_mutation Mutations::Boards::Update
mount_mutation Mutations::Boards::Issues::IssueMoveList mount_mutation Mutations::Boards::Issues::IssueMoveList
mount_mutation Mutations::Boards::Lists::Create mount_mutation Mutations::Boards::Lists::Create
mount_mutation Mutations::Boards::Lists::Update mount_mutation Mutations::Boards::Lists::Update
......
...@@ -14,6 +14,7 @@ class List < ApplicationRecord ...@@ -14,6 +14,7 @@ class List < ApplicationRecord
validates :label_id, uniqueness: { scope: :board_id }, if: :label? validates :label_id, uniqueness: { scope: :board_id }, if: :label?
scope :preload_associated_models, -> { preload(:board, label: :priorities) } scope :preload_associated_models, -> { preload(:board, label: :priorities) }
scope :without_types, ->(list_types) { where.not(list_type: list_types) }
alias_method :preferences, :list_user_preferences alias_method :preferences, :list_user_preferences
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class CurrentBoardEntity < Grape::Entity class CurrentBoardEntity < Grape::Entity
expose :id expose :id
expose :name expose :name
expose :hide_backlog_list
expose :hide_closed_list
end end
CurrentBoardEntity.prepend_if_ee('EE::CurrentBoardEntity') CurrentBoardEntity.prepend_if_ee('EE::CurrentBoardEntity')
...@@ -9,7 +9,26 @@ module Boards ...@@ -9,7 +9,26 @@ module Boards
end end
lists = board.lists.preload_associated_models lists = board.lists.preload_associated_models
params[:list_id].present? ? lists.where(id: params[:list_id]) : lists # rubocop: disable CodeReuse/ActiveRecord
return lists.id_in(params[:list_id]) if params[:list_id].present?
list_types = unavailable_list_types_for(board)
lists.without_types(list_types)
end
private
def unavailable_list_types_for(board)
hidden_lists_for(board)
end
def hidden_lists_for(board)
hidden = []
hidden << ::List.list_types[:backlog] if board.hide_backlog_list
hidden << ::List.list_types[:closed] if board.hide_closed_list
hidden
end end
end end
end end
......
...@@ -195,7 +195,7 @@ ...@@ -195,7 +195,7 @@
#js-board-labels-toggle #js-board-labels-toggle
- if current_user - if current_user
#js-board-epics-swimlanes-toggle #js-board-epics-swimlanes-toggle
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } } .js-board-config{ data: { can_admin_list: user_can_admin_list.to_s, has_scope: board.scoped?.to_s } }
- if user_can_admin_list - if user_can_admin_list
- if Feature.enabled?(:board_new_list, board.resource_parent, default_enabled: :yaml) - if Feature.enabled?(:board_new_list, board.resource_parent, default_enabled: :yaml)
.js-create-column-trigger{ data: board_list_data } .js-create-column-trigger{ data: board_list_data }
......
import Vue from 'vue';
import ConfigToggle from './components/config_toggle.vue';
export default (boardsStore) => {
const configEl = document.querySelector('.js-board-config');
if (configEl) {
gl.boardConfigToggle = new Vue({
el: configEl,
render(h) {
return h(ConfigToggle, {
props: {
boardsStore,
canAdminList: configEl.hasAttribute('data-can-admin-list'),
hasScope: configEl.hasAttribute('data-has-scope'),
},
});
},
});
}
};
...@@ -47,8 +47,6 @@ module EE ...@@ -47,8 +47,6 @@ module EE
base.validates :list_type, base.validates :list_type,
exclusion: { in: %w[iteration], message: -> (_object, _data) { _('Iteration lists not available with your current license') } }, exclusion: { in: %w[iteration], message: -> (_object, _data) { _('Iteration lists not available with your current license') } },
unless: -> { board&.resource_parent&.feature_available?(:board_iteration_lists) } unless: -> { board&.resource_parent&.feature_available?(:board_iteration_lists) }
base.scope :without_types, ->(list_types) { where.not(list_type: list_types) }
end end
def assignee=(user) def assignee=(user)
......
...@@ -11,8 +11,6 @@ module EE ...@@ -11,8 +11,6 @@ module EE
expose :milestone, using: BoardMilestoneEntity expose :milestone, using: BoardMilestoneEntity
expose :assignee, using: BoardAssigneeEntity expose :assignee, using: BoardAssigneeEntity
expose :labels, using: BoardLabelEntity expose :labels, using: BoardLabelEntity
expose :hide_backlog_list
expose :hide_closed_list
end end
end end
end end
...@@ -16,21 +16,13 @@ module EE ...@@ -16,21 +16,13 @@ module EE
private private
def unavailable_list_types_for(board) def unavailable_list_types_for(board)
list_types = hidden_lists_for(board) + unlicensed_lists_for(board) list_types = super
list_types += unlicensed_lists_for(board)
list_types << ::List.list_types[:iteration] if ::Feature.disabled?(:iteration_board_lists, board.resource_parent) list_types << ::List.list_types[:iteration] if ::Feature.disabled?(:iteration_board_lists, board.resource_parent)
list_types.uniq list_types.uniq
end end
def hidden_lists_for(board)
hidden = []
hidden << ::List.list_types[:backlog] if board.hide_backlog_list
hidden << ::List.list_types[:closed] if board.hide_closed_list
hidden
end
def unlicensed_lists_for(board) def unlicensed_lists_for(board)
parent = board.resource_parent parent = board.resource_parent
......
...@@ -14,8 +14,6 @@ module EE ...@@ -14,8 +14,6 @@ module EE
params.delete(:label_ids) params.delete(:label_ids)
params.delete(:labels) params.delete(:labels)
params.delete(:weight) params.delete(:weight)
params.delete(:hide_backlog_list)
params.delete(:hide_closed_list)
end end
filter_assignee filter_assignee
......
---
title: Fix updating of board's hide_backlog_list and hide_closed_list settings when
on an unlicensed EE instance
merge_request: 51561
author:
type: fixed
...@@ -6,26 +6,29 @@ RSpec.describe 'Scoped issue boards', :js do ...@@ -6,26 +6,29 @@ RSpec.describe 'Scoped issue boards', :js do
include FilteredSearchHelpers include FilteredSearchHelpers
include MobileHelpers include MobileHelpers
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) } let_it_be(:project) { create(:project, :public, namespace: group) }
let(:project_2) { create(:project, :public, namespace: group) } let_it_be(:project_2) { create(:project, :public, namespace: group) }
let!(:project_label) { create(:label, project: project, name: 'Planning') } let_it_be(:project_label) { create(:label, project: project, name: 'Planning') }
let!(:group_label) { create(:group_label, group: group, name: 'Group Label') } let_it_be(:group_label) { create(:group_label, group: group, name: 'Group Label') }
let!(:milestone) { create(:milestone, project: project) } let_it_be(:milestone) { create(:milestone, project: project) }
let!(:board) { create(:board, project: project, name: 'Project board') } let_it_be(:board) { create(:board, project: project, name: 'Project board') }
let!(:group_board) { create(:board, group: group, name: 'Group board') } let_it_be(:group_board) { create(:board, group: group, name: 'Group board') }
let!(:filtered_board) { create(:board, project: project_2, name: 'Filtered board', milestone: milestone, assignee: user, weight: 2) } let_it_be(:filtered_board) { create(:board, project: project_2, name: 'Filtered board', milestone: milestone, assignee: user, weight: 2) }
let!(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let!(:issue_milestone) { create(:closed_issue, project: project, milestone: milestone) } let_it_be(:issue_milestone) { create(:closed_issue, project: project, milestone: milestone) }
let!(:assigned_issue) { create(:issue, project: project, assignees: [user]) } let_it_be(:assigned_issue) { create(:issue, project: project, assignees: [user]) }
let(:edit_board) { find('.btn', text: 'Edit board') } let(:edit_board) { find('.btn', text: 'Edit board') }
let(:view_scope) { find('.btn', text: 'View scope') } let(:view_scope) { find('.btn', text: 'View scope') }
let(:board_title) { find('.boards-selector-wrapper .dropdown-menu-toggle') } let(:board_title) { find('.boards-selector-wrapper .dropdown-menu-toggle') }
before do before do
allow_any_instance_of(ApplicationHelper).to receive(:collapsed_sidebar?).and_return(true) allow_next_instance_of(ApplicationHelper) do |helper|
allow(helper).to receive(:collapsed_sidebar?).and_return(true)
end
stub_licensed_features(scoped_issue_board: true) stub_licensed_features(scoped_issue_board: true)
end end
......
...@@ -36,19 +36,6 @@ RSpec.describe List do ...@@ -36,19 +36,6 @@ RSpec.describe List do
end end
end end
describe '.without_types' do
it 'exclude lists of given types' do
board = create(:list, list_type: :label).board
# closed list is created by default
backlog_list = create(:list, list_type: :backlog, board: board)
exclude_type = [described_class.list_types[:label], described_class.list_types[:closed]]
lists = described_class.without_types(exclude_type)
expect(lists.where(board: board)).to match_array([backlog_list])
end
end
context 'when it is a milestone type' do context 'when it is a milestone type' do
let(:milestone) { build(:milestone, title: 'awesome-release') } let(:milestone) { build(:milestone, title: 'awesome-release') }
......
...@@ -46,12 +46,14 @@ RSpec.describe Boards::UpdateService, services: true do ...@@ -46,12 +46,14 @@ RSpec.describe Boards::UpdateService, services: true do
it 'filters unpermitted params when scoped issue board is not enabled' do it 'filters unpermitted params when scoped issue board is not enabled' do
stub_licensed_features(scoped_issue_board: false) stub_licensed_features(scoped_issue_board: false)
params = { milestone_id: double, iteration_id: double, assignee_id: double, label_ids: double, weight: double, hide_backlog_list: true, hide_closed_list: true } unpermitted_params = { milestone_id: double, iteration_id: double, assignee_id: double, label_ids: double, weight: double }
permitted_params = { hide_backlog_list: true, hide_closed_list: true }
params = unpermitted_params.merge(permitted_params)
service = described_class.new(project, double, params) service = described_class.new(project, double, params)
service.execute(board) service.execute(board)
expected_attributes = { milestone: nil, iteration: nil, assignee: nil, labels: [], hide_backlog_list: false, hide_closed_list: false } expected_attributes = { milestone: nil, iteration: nil, assignee: nil, labels: [], hide_backlog_list: true, hide_closed_list: true }
expect(board.reload).to have_attributes(expected_attributes) expect(board.reload).to have_attributes(expected_attributes)
end end
......
...@@ -90,26 +90,6 @@ RSpec.describe Boards::Lists::ListService do ...@@ -90,26 +90,6 @@ RSpec.describe Boards::Lists::ListService do
end end
end end
shared_examples 'hidden lists' do
let!(:list) { create(:list, board: board, label: label) }
context 'when hide_backlog_list is true' do
it 'hides backlog list' do
board.update(hide_backlog_list: true)
expect(execute_service).to match_array([board.closed_list, list])
end
end
context 'when hide_closed_list is true' do
it 'hides closed list' do
board.update(hide_closed_list: true)
expect(execute_service).to match_array([board.backlog_list, list])
end
end
end
context 'when board parent is a project' do context 'when board parent is a project' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -120,7 +100,6 @@ RSpec.describe Boards::Lists::ListService do ...@@ -120,7 +100,6 @@ RSpec.describe Boards::Lists::ListService do
it_behaves_like 'list service for board with assignee lists' it_behaves_like 'list service for board with assignee lists'
it_behaves_like 'list service for board with milestone lists' it_behaves_like 'list service for board with milestone lists'
it_behaves_like 'list service for board with iteration lists' it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists'
end end
context 'when board parent is a group' do context 'when board parent is a group' do
...@@ -133,7 +112,6 @@ RSpec.describe Boards::Lists::ListService do ...@@ -133,7 +112,6 @@ RSpec.describe Boards::Lists::ListService do
it_behaves_like 'list service for board with assignee lists' it_behaves_like 'list service for board with assignee lists'
it_behaves_like 'list service for board with milestone lists' it_behaves_like 'list service for board with milestone lists'
it_behaves_like 'list service for board with iteration lists' it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists'
end end
end end
end end
...@@ -14,20 +14,12 @@ module QA ...@@ -14,20 +14,12 @@ module QA
view 'ee/app/assets/javascripts/boards/components/board_scope.vue' do view 'ee/app/assets/javascripts/boards/components/board_scope.vue' do
element :board_scope_modal element :board_scope_modal
end end
view 'ee/app/assets/javascripts/boards/components/config_toggle.vue' do
element :boards_config_button
end
end end
end end
def board_scope_modal def board_scope_modal
find_element(:board_scope_modal) find_element(:board_scope_modal)
end end
def click_boards_config_button
click_element(:boards_config_button)
end
end end
end end
end end
......
...@@ -43,6 +43,10 @@ module QA ...@@ -43,6 +43,10 @@ module QA
element :focus_mode_button element :focus_mode_button
end end
view 'app/assets/javascripts/boards/components/config_toggle.vue' do
element :boards_config_button
end
# The `focused_board` method does not use `find_element` with an element defined # The `focused_board` method does not use `find_element` with an element defined
# with the attribute `data-qa-selector` since such element is not unique when the # with the attribute `data-qa-selector` since such element is not unique when the
# `is-focused` class is not set, and it was not possible to find a better solution. # `is-focused` class is not set, and it was not possible to find a better solution.
...@@ -82,6 +86,10 @@ module QA ...@@ -82,6 +86,10 @@ module QA
end end
end end
def click_boards_config_button
click_element(:boards_config_button)
end
def click_boards_dropdown_button def click_boards_dropdown_button
# The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`, # The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`,
# so it wasn't possible to add a `data-qa-selector` to it. # so it wasn't possible to add a `data-qa-selector` to it.
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Boards::Update do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:board) { create(:board, project: project) }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_board) { subject[:board] }
let(:mutation_params) do
{
id: board.to_global_id,
hide_backlog_list: true,
hide_closed_list: false
}
end
subject { mutation.resolve(**mutation_params) }
specify { expect(described_class).to require_graphql_authorizations(:admin_board) }
describe '#resolve' do
context 'when the user cannot admin the board' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'with invalid params' do
it 'raises an error' do
mutation_params[:id] = project.to_global_id
expect { subject }.to raise_error(::GraphQL::CoercionError)
end
end
context 'when user can update board' do
before do
board.resource_parent.add_reporter(user)
end
it 'updates board with correct values' do
expected_attributes = {
hide_backlog_list: true,
hide_closed_list: false
}
subject
expect(board.reload).to have_attributes(expected_attributes)
end
end
end
end
...@@ -17,6 +17,19 @@ RSpec.describe List do ...@@ -17,6 +17,19 @@ RSpec.describe List do
it { is_expected.to validate_presence_of(:list_type) } it { is_expected.to validate_presence_of(:list_type) }
end end
describe '.without_types' do
it 'exclude lists of given types' do
board = create(:list, list_type: :label).board
# closed list is created by default
backlog_list = create(:list, list_type: :backlog, board: board)
exclude_type = [described_class.list_types[:label], described_class.list_types[:closed]]
lists = described_class.without_types(exclude_type)
expect(lists.where(board: board)).to match_array([backlog_list])
end
end
describe '#update_preferences_for' do describe '#update_preferences_for' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:list) { create(:list) } let(:list) { create(:list) }
......
...@@ -8,6 +8,26 @@ RSpec.describe Boards::Lists::ListService do ...@@ -8,6 +8,26 @@ RSpec.describe Boards::Lists::ListService do
describe '#execute' do describe '#execute' do
let(:service) { described_class.new(parent, user) } let(:service) { described_class.new(parent, user) }
shared_examples 'hidden lists' do
let!(:list) { create(:list, board: board, label: label) }
context 'when hide_backlog_list is true' do
it 'hides backlog list' do
board.update!(hide_backlog_list: true)
expect(service.execute(board)).to match_array([board.closed_list, list])
end
end
context 'when hide_closed_list is true' do
it 'hides closed list' do
board.update!(hide_closed_list: true)
expect(service.execute(board)).to match_array([board.backlog_list, list])
end
end
end
context 'when board parent is a project' do context 'when board parent is a project' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:board) { create(:board, project: project) } let(:board) { create(:board, project: project) }
...@@ -16,6 +36,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -16,6 +36,7 @@ RSpec.describe Boards::Lists::ListService do
let(:parent) { project } let(:parent) { project }
it_behaves_like 'lists list service' it_behaves_like 'lists list service'
it_behaves_like 'hidden lists'
end end
context 'when board parent is a group' do context 'when board parent is a group' do
...@@ -26,6 +47,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -26,6 +47,7 @@ RSpec.describe Boards::Lists::ListService do
let(:parent) { group } let(:parent) { group }
it_behaves_like 'lists list service' it_behaves_like 'lists list service'
it_behaves_like 'hidden lists'
end end
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