Commit ec5808a6 authored by Michael Kozono's avatar Michael Kozono

Merge branch 'issue_54042' into 'master'

Let project reporters create issue from group boards

Closes #54042

See merge request gitlab-org/gitlab-ce!29866
parents 24de5d65 13fc0efa
...@@ -114,7 +114,7 @@ export default { ...@@ -114,7 +114,7 @@ export default {
name="issue_title" name="issue_title"
autocomplete="off" autocomplete="off"
/> />
<project-select v-if="groupId" :group-id="groupId" /> <project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix prepend-top-10"> <div class="clearfix prepend-top-10">
<gl-button <gl-button
ref="submit-button" ref="submit-button"
......
...@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue'; ...@@ -6,6 +6,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import Api from '../../api'; import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
export default { export default {
name: 'BoardProjectSelect', name: 'BoardProjectSelect',
...@@ -19,6 +20,10 @@ export default { ...@@ -19,6 +20,10 @@ export default {
required: true, required: true,
default: 0, default: 0,
}, },
list: {
type: Object,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -49,6 +54,12 @@ export default { ...@@ -49,6 +54,12 @@ export default {
selectable: true, selectable: true,
data: (term, callback) => { data: (term, callback) => {
this.loading = true; this.loading = true;
const additionalAttrs = {};
if (this.list.type && this.list.type !== 'backlog') {
additionalAttrs.min_access_level = featureAccessLevel.EVERYONE;
}
return Api.groupProjects( return Api.groupProjects(
this.groupId, this.groupId,
term, term,
...@@ -56,6 +67,7 @@ export default { ...@@ -56,6 +67,7 @@ export default {
with_issues_enabled: true, with_issues_enabled: true,
with_shared: false, with_shared: false,
include_subgroups: true, include_subgroups: true,
...additionalAttrs,
}, },
projects => { projects => {
this.loading = false; this.loading = false;
......
...@@ -16,7 +16,7 @@ export const visibilityLevelDescriptions = { ...@@ -16,7 +16,7 @@ export const visibilityLevelDescriptions = {
), ),
}; };
const featureAccessLevel = { export const featureAccessLevel = {
NOT_ENABLED: 0, NOT_ENABLED: 0,
PROJECT_MEMBERS: 10, PROJECT_MEMBERS: 10,
EVERYONE: 20, EVERYONE: 20,
......
...@@ -10,7 +10,7 @@ module BoardsHelper ...@@ -10,7 +10,7 @@ module BoardsHelper
boards_endpoint: @boards_endpoint, boards_endpoint: @boards_endpoint,
lists_endpoint: board_lists_path(board), lists_endpoint: board_lists_path(board),
board_id: board.id, board_id: board.id,
disabled: "#{!can?(current_user, :admin_list, current_board_parent)}", disabled: (!can?(current_user, :create_non_backlog_issues, board)).to_s,
issue_link_base: build_issue_link_base, issue_link_base: build_issue_link_base,
root_path: root_path, root_path: root_path,
bulk_update_path: @bulk_issues_path, bulk_update_path: @bulk_issues_path,
......
# frozen_string_literal: true # frozen_string_literal: true
class BoardPolicy < BasePolicy class BoardPolicy < BasePolicy
include FindGroupProjects
delegate { @subject.parent } delegate { @subject.parent }
condition(:is_group_board) { @subject.group_board? } condition(:is_group_board) { @subject.group_board? }
...@@ -13,4 +15,20 @@ class BoardPolicy < BasePolicy ...@@ -13,4 +15,20 @@ class BoardPolicy < BasePolicy
enable :read_milestone enable :read_milestone
enable :read_issue enable :read_issue
end end
condition(:reporter_of_group_projects) do
next unless @user
group_projects_for(user: @user, group: @subject.parent)
.visible_to_user_and_access_level(@user, ::Gitlab::Access::REPORTER)
.exists?
end
rule { is_group_board & reporter_of_group_projects }.policy do
enable :create_non_backlog_issues
end
rule { is_project_board & can?(:admin_issue) }.policy do
enable :create_non_backlog_issues
end
end end
# frozen_string_literal: true
module FindGroupProjects
extend ActiveSupport::Concern
def group_projects_for(user:, group:)
GroupProjectsFinder.new(
group: group,
current_user: user,
options: { include_subgroups: true, only_owned: true }
).execute
end
end
# frozen_string_literal: true # frozen_string_literal: true
class GroupPolicy < BasePolicy class GroupPolicy < BasePolicy
include FindGroupProjects
desc "Group is public" desc "Group is public"
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? } condition(:public_group) { @subject.public? }
...@@ -22,7 +24,7 @@ class GroupPolicy < BasePolicy ...@@ -22,7 +24,7 @@ class GroupPolicy < BasePolicy
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) } condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:has_projects) do condition(:has_projects) do
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any? group_projects_for(user: @user, group: @subject).any?
end end
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
......
---
title: Let project reporters create issue from group boards
merge_request: 29866
author:
type: fixed
...@@ -156,7 +156,8 @@ Parameters: ...@@ -156,7 +156,8 @@ Parameters:
| `with_issues_enabled` | boolean | no | Limit by projects with issues feature enabled. Default is `false` | | `with_issues_enabled` | boolean | no | Limit by projects with issues feature enabled. Default is `false` |
| `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` | | `with_merge_requests_enabled` | boolean | no | Limit by projects with merge requests feature enabled. Default is `false` |
| `with_shared` | boolean | no | Include projects shared to this group. Default is `true` | | `with_shared` | boolean | no | Include projects shared to this group. Default is `true` |
| `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` | | `include_subgroups` | boolean | no | Include projects in subgroups of this group. Default is `false` |
| `min_access_level` | integer | no | Limit to projects where current user has at least this [access level](members.md) |
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) | | `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only) |
| `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` | | `with_security_reports` | boolean | no | **(ULTIMATE)** Return only projects that have security reports artifacts present in any of their builds. This means "projects with security reports enabled". Default is `false` |
......
...@@ -75,6 +75,7 @@ module API ...@@ -75,6 +75,7 @@ module API
).execute ).execute
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled] projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled]
projects = projects.visible_to_user_and_access_level(current_user, params[:min_access_level]) if params[:min_access_level]
projects = reorder_projects(projects) projects = reorder_projects(projects)
paginate(projects) paginate(projects)
end end
...@@ -213,6 +214,7 @@ module API ...@@ -213,6 +214,7 @@ module API
optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature'
optional :with_shared, type: Boolean, default: true, desc: 'Include projects shared to this group' optional :with_shared, type: Boolean, default: true, desc: 'Include projects shared to this group'
optional :include_subgroups, type: Boolean, default: false, desc: 'Includes projects in subgroups of this group' optional :include_subgroups, type: Boolean, default: false, desc: 'Includes projects in subgroups of this group'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user on projects'
use :pagination use :pagination
use :with_custom_attributes use :with_custom_attributes
......
...@@ -127,4 +127,44 @@ describe 'Issue Boards new issue', :js do ...@@ -127,4 +127,44 @@ describe 'Issue Boards new issue', :js do
end end
end end
end end
context 'group boards' do
set(:group) { create(:group, :public) }
set(:project) { create(:project, namespace: group) }
set(:group_board) { create(:board, group: group) }
set(:list) { create(:list, board: group_board, position: 0) }
context 'for unauthorized users' do
before do
sign_in(user)
visit group_board_path(group, group_board)
wait_for_requests
end
it 'displays new issue button in open list' do
expect(first('.board')).to have_selector('.issue-count-badge-add-button', count: 1)
end
it 'does not display new issue button in label list' do
page.within('.board.is-draggable') do
expect(page).not_to have_selector('.issue-count-badge-add-button')
end
end
end
context 'for authorized users' do
it 'display new issue button in label list' do
project = create(:project, namespace: group)
project.add_reporter(user)
sign_in(user)
visit group_board_path(group, group_board)
wait_for_requests
page.within('.board.is-draggable') do
expect(page).to have_selector('.issue-count-badge-add-button')
end
end
end
end
end end
...@@ -40,7 +40,7 @@ describe BoardsHelper do ...@@ -40,7 +40,7 @@ describe BoardsHelper do
assign(:project, project) assign(:project, project)
allow(helper).to receive(:current_user) { user } allow(helper).to receive(:current_user) { user }
allow(helper).to receive(:can?).with(user, :admin_list, project).and_return(true) allow(helper).to receive(:can?).with(user, :create_non_backlog_issues, board).and_return(true)
end end
it 'returns a board_lists_path as lists_endpoint' do it 'returns a board_lists_path as lists_endpoint' do
......
...@@ -56,4 +56,57 @@ describe BoardPolicy do ...@@ -56,4 +56,57 @@ describe BoardPolicy do
end end
end end
end end
context 'create_non_backlog_issues' do
context 'for project boards' do
let!(:current_user) { create(:user) }
subject { described_class.new(current_user, project_board) }
context 'when user can admin project issues' do
it 'allows to add non backlog issues from issue board' do
project.add_reporter(current_user)
expect_allowed(:create_non_backlog_issues)
end
end
context 'when user cannot admin project issues' do
it 'does not allow to add non backlog issues from issue board' do
project.add_guest(current_user)
expect_disallowed(:create_non_backlog_issues)
end
end
end
context 'for group boards' do
let!(:current_user) { create(:user) }
let!(:project_1) { create(:project, namespace: group) }
let!(:project_2) { create(:project, namespace: group) }
let!(:group_board) { create(:board, group: group) }
subject { described_class.new(current_user, group_board) }
before do
project_1.add_guest(current_user)
end
context 'when user is at least reporter in one of the child projects' do
it 'allows to add non backlog issues from issue board' do
project_2.add_reporter(current_user)
expect_allowed(:create_non_backlog_issues)
end
end
context 'when user is not a reporter from any child projects' do
it 'does not allow to add non backlog issues from issue board' do
project_2.add_guest(current_user)
expect_disallowed(:create_non_backlog_issues)
end
end
end
end
end end
...@@ -483,6 +483,22 @@ describe API::Groups do ...@@ -483,6 +483,22 @@ describe API::Groups do
describe "GET /groups/:id/projects" do describe "GET /groups/:id/projects" do
context "when authenticated as user" do context "when authenticated as user" do
context 'with min access level' do
it 'returns projects with min access level or higher' do
group_guest = create(:user)
group1.add_guest(group_guest)
project4 = create(:project, group: group1)
project1.add_guest(group_guest)
project3.add_reporter(group_guest)
project4.add_developer(group_guest)
get api("/groups/#{group1.id}/projects", group_guest), params: { min_access_level: Gitlab::Access::REPORTER }
project_ids = json_response.map { |proj| proj['id'] }
expect(project_ids).to match_array([project3.id, project4.id])
end
end
it "returns the group's projects" do it "returns the group's projects" do
get api("/groups/#{group1.id}/projects", user1) get api("/groups/#{group1.id}/projects", user1)
......
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