Let project reporters create issue from group boards
The current state of group issue boards does not show the "Add issues" button on the UI for users that are reporters of group child projects.
This commit is contained in:
parent
24de5d6573
commit
13fc0efa57
|
@ -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';
|
||||||
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 {
|
||||||
required: true,
|
required: true,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
list: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -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 {
|
||||||
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 = {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
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
|
||||||
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,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# 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
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 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
|
|
@ -1,6 +1,8 @@
|
||||||
# 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
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Let project reporters create issue from group boards
|
||||||
|
merge_request: 29866
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -157,6 +157,7 @@ Parameters:
|
||||||
| `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
|
||||||
).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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue