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';
import 'ee_else_ce/boards/models/list';
import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import {
setWeightFetchingState,
setEpicFetchingState,
......@@ -40,6 +39,7 @@ import {
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
import boardConfigToggle from './config_toggle';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
Vue.use(VueApollo);
......
......@@ -24,6 +24,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Boards::Create
mount_mutation Mutations::Boards::Destroy
mount_mutation Mutations::Boards::Update
mount_mutation Mutations::Boards::Issues::IssueMoveList
mount_mutation Mutations::Boards::Lists::Create
mount_mutation Mutations::Boards::Lists::Update
......
......@@ -14,6 +14,7 @@ class List < ApplicationRecord
validates :label_id, uniqueness: { scope: :board_id }, if: :label?
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
......
......@@ -3,6 +3,8 @@
class CurrentBoardEntity < Grape::Entity
expose :id
expose :name
expose :hide_backlog_list
expose :hide_closed_list
end
CurrentBoardEntity.prepend_if_ee('EE::CurrentBoardEntity')
......@@ -9,7 +9,26 @@ module Boards
end
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
......
......@@ -195,7 +195,7 @@
#js-board-labels-toggle
- if current_user
#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 Feature.enabled?(:board_new_list, board.resource_parent, default_enabled: :yaml)
.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
base.validates :list_type,
exclusion: { in: %w[iteration], message: -> (_object, _data) { _('Iteration lists not available with your current license') } },
unless: -> { board&.resource_parent&.feature_available?(:board_iteration_lists) }
base.scope :without_types, ->(list_types) { where.not(list_type: list_types) }
end
def assignee=(user)
......
......@@ -11,8 +11,6 @@ module EE
expose :milestone, using: BoardMilestoneEntity
expose :assignee, using: BoardAssigneeEntity
expose :labels, using: BoardLabelEntity
expose :hide_backlog_list
expose :hide_closed_list
end
end
end
......@@ -16,21 +16,13 @@ module EE
private
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.uniq
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)
parent = board.resource_parent
......
......@@ -14,8 +14,6 @@ module EE
params.delete(:label_ids)
params.delete(:labels)
params.delete(:weight)
params.delete(:hide_backlog_list)
params.delete(:hide_closed_list)
end
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
include FilteredSearchHelpers
include MobileHelpers
let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
let(:project_2) { create(:project, :public, namespace: group) }
let!(:project_label) { create(:label, project: project, name: 'Planning') }
let!(:group_label) { create(:group_label, group: group, name: 'Group Label') }
let!(:milestone) { create(:milestone, project: project) }
let!(:board) { create(:board, project: project, name: 'Project board') }
let!(: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!(:issue) { create(:issue, project: project) }
let!(:issue_milestone) { create(:closed_issue, project: project, milestone: milestone) }
let!(:assigned_issue) { create(:issue, project: project, assignees: [user]) }
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:project_2) { create(:project, :public, namespace: group) }
let_it_be(:project_label) { create(:label, project: project, name: 'Planning') }
let_it_be(:group_label) { create(:group_label, group: group, name: 'Group Label') }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:board) { create(:board, project: project, name: 'Project board') }
let_it_be(:group_board) { create(:board, group: group, name: 'Group board') }
let_it_be(:filtered_board) { create(:board, project: project_2, name: 'Filtered board', milestone: milestone, assignee: user, weight: 2) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:issue_milestone) { create(:closed_issue, project: project, milestone: milestone) }
let_it_be(:assigned_issue) { create(:issue, project: project, assignees: [user]) }
let(:edit_board) { find('.btn', text: 'Edit board') }
let(:view_scope) { find('.btn', text: 'View scope') }
let(:board_title) { find('.boards-selector-wrapper .dropdown-menu-toggle') }
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)
end
......
......@@ -36,19 +36,6 @@ RSpec.describe List do
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
let(:milestone) { build(:milestone, title: 'awesome-release') }
......
......@@ -46,12 +46,14 @@ RSpec.describe Boards::UpdateService, services: true do
it 'filters unpermitted params when scoped issue board is not enabled' do
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.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)
end
......
......@@ -90,26 +90,6 @@ RSpec.describe Boards::Lists::ListService do
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
let(:user) { create(:user) }
let(:project) { create(:project) }
......@@ -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 milestone lists'
it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists'
end
context 'when board parent is a group' 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 milestone lists'
it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists'
end
end
end
......@@ -14,20 +14,12 @@ module QA
view 'ee/app/assets/javascripts/boards/components/board_scope.vue' do
element :board_scope_modal
end
view 'ee/app/assets/javascripts/boards/components/config_toggle.vue' do
element :boards_config_button
end
end
end
def board_scope_modal
find_element(:board_scope_modal)
end
def click_boards_config_button
click_element(:boards_config_button)
end
end
end
end
......
......@@ -43,6 +43,10 @@ module QA
element :focus_mode_button
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
# 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.
......@@ -82,6 +86,10 @@ module QA
end
end
def click_boards_config_button
click_element(:boards_config_button)
end
def click_boards_dropdown_button
# The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`,
# 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
it { is_expected.to validate_presence_of(:list_type) }
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
let(:user) { create(:user) }
let(:list) { create(:list) }
......
......@@ -8,6 +8,26 @@ RSpec.describe Boards::Lists::ListService do
describe '#execute' do
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
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
......@@ -16,6 +36,7 @@ RSpec.describe Boards::Lists::ListService do
let(:parent) { project }
it_behaves_like 'lists list service'
it_behaves_like 'hidden lists'
end
context 'when board parent is a group' do
......@@ -26,6 +47,7 @@ RSpec.describe Boards::Lists::ListService do
let(:parent) { group }
it_behaves_like 'lists list service'
it_behaves_like 'hidden lists'
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