Commit b0c9b7e2 authored by Mario de la Ossa's avatar Mario de la Ossa Committed by Peter Leitzen

Add Sprints CreateService

Adds Sprints::CreateService and the SprintPolicy object
parent 30cb62ca
...@@ -22,6 +22,7 @@ class License < ApplicationRecord ...@@ -22,6 +22,7 @@ class License < ApplicationRecord
group_webhooks group_webhooks
issuable_default_templates issuable_default_templates
issue_weights issue_weights
iterations
jenkins_integration jenkins_integration
ldap_group_sync ldap_group_sync
member_lock member_lock
......
...@@ -11,6 +11,7 @@ module EE ...@@ -11,6 +11,7 @@ module EE
with_scope :subject with_scope :subject
condition(:ldap_synced) { @subject.ldap_synced? } condition(:ldap_synced) { @subject.ldap_synced? }
condition(:epics_available) { @subject.feature_available?(:epics) } condition(:epics_available) { @subject.feature_available?(:epics) }
condition(:iterations_available) { @subject.feature_available?(:iterations) }
condition(:subepics_available) { @subject.feature_available?(:subepics) } condition(:subepics_available) { @subject.feature_available?(:subepics) }
condition(:contribution_analytics_available) do condition(:contribution_analytics_available) do
@subject.feature_available?(:contribution_analytics) @subject.feature_available?(:contribution_analytics)
...@@ -117,6 +118,13 @@ module EE ...@@ -117,6 +118,13 @@ module EE
rule { can?(:read_group) & epics_available }.enable :read_epic rule { can?(:read_group) & epics_available }.enable :read_epic
rule { can?(:read_group) & iterations_available }.enable :read_iteration
rule { developer & iterations_available }.policy do
enable :create_iteration
enable :admin_iteration
end
rule { reporter & epics_available }.policy do rule { reporter & epics_available }.policy do
enable :create_epic enable :create_epic
enable :admin_epic enable :admin_epic
......
...@@ -14,6 +14,7 @@ module EE ...@@ -14,6 +14,7 @@ module EE
license_management license_management
feature_flag feature_flag
feature_flags_client feature_flags_client
iteration
].freeze ].freeze
prepended do prepended do
...@@ -32,6 +33,9 @@ module EE ...@@ -32,6 +33,9 @@ module EE
with_scope :subject with_scope :subject
condition(:packages_disabled) { !@subject.packages_enabled } condition(:packages_disabled) { !@subject.packages_enabled }
with_scope :subject
condition(:iterations_available) { @subject.feature_available?(:iterations) }
with_scope :subject with_scope :subject
condition(:requirements_available) { @subject.feature_available?(:requirements) } condition(:requirements_available) { @subject.feature_available?(:requirements) }
...@@ -153,6 +157,8 @@ module EE ...@@ -153,6 +157,8 @@ module EE
enable :read_issue_link enable :read_issue_link
end end
rule { can?(:guest_access) & iterations_available }.enable :read_iteration
rule { can?(:reporter_access) }.policy do rule { can?(:reporter_access) }.policy do
enable :admin_board enable :admin_board
enable :read_deploy_board enable :read_deploy_board
...@@ -177,8 +183,15 @@ module EE ...@@ -177,8 +183,15 @@ module EE
enable :admin_feature_flags_user_lists enable :admin_feature_flags_user_lists
end end
rule { can?(:developer_access) & iterations_available }.policy do
enable :create_iteration
enable :admin_iteration
end
rule { can?(:public_access) }.enable :read_package rule { can?(:public_access) }.enable :read_package
rule { can?(:read_project) & iterations_available }.enable :read_iteration
rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability
rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train
...@@ -191,6 +204,10 @@ module EE ...@@ -191,6 +204,10 @@ module EE
enable :admin_vulnerability_issue_link enable :admin_vulnerability_issue_link
end end
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin_destroy(:iteration))
end
rule { threat_monitoring_enabled & (auditor | can?(:developer_access)) }.enable :read_threat_monitoring rule { threat_monitoring_enabled & (auditor | can?(:developer_access)) }.enable :read_threat_monitoring
rule { dependency_scanning_enabled & can?(:download_code) }.enable :read_dependencies rule { dependency_scanning_enabled & can?(:download_code) }.enable :read_dependencies
......
# frozen_string_literal: true
class SprintPolicy < BasePolicy
delegate { @subject.resource_parent }
end
# frozen_string_literal: true
module Iterations
class CreateService
include Gitlab::Allowable
# Parent can either a group or a project
attr_accessor :parent, :current_user, :params
def initialize(parent, user, params = {})
@parent, @current_user, @params = parent, user, params.dup
end
def execute
return ::ServiceResponse.error(message: _('Operation not allowed'), http_status: 403) unless
parent.feature_available?(:iterations) && can?(current_user, :create_iteration, parent)
iteration = parent.sprints.new(params)
if iteration.save
::ServiceResponse.success(message: _('New iteration created'), payload: { iteration: iteration })
else
::ServiceResponse.error(message: _('Error creating new iteration'), payload: { errors: iteration.errors })
end
end
end
end
...@@ -21,6 +21,52 @@ describe GroupPolicy do ...@@ -21,6 +21,52 @@ describe GroupPolicy do
it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic) } it { is_expected.to be_allowed(:read_epic, :create_epic, :admin_epic, :destroy_epic) }
end end
context 'when iterations feature is disabled' do
let(:current_user) { owner }
before do
stub_licensed_features(iterations: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when iterations feature is enabled' do
before do
stub_licensed_features(iterations: true)
end
context 'when user is a developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a guest' do
let(:current_user) { guest }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when project is private' do
let(:group) { create(:group, :public, :owner_subgroup_creation_only) }
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
end
end
context 'when cluster deployments is available' do context 'when cluster deployments is available' do
let(:current_user) { maintainer } let(:current_user) { maintainer }
......
...@@ -45,7 +45,7 @@ describe ProjectPolicy do ...@@ -45,7 +45,7 @@ describe ProjectPolicy do
%i[ %i[
download_code download_wiki_code read_project read_board read_list download_code download_wiki_code read_project read_board read_list
read_project_for_iids read_issue_iid read_merge_request_iid read_wiki read_project_for_iids read_issue_iid read_merge_request_iid read_wiki
read_issue read_label read_issue_link read_milestone read_issue read_label read_issue_link read_milestone read_iteration
read_snippet read_project_member read_note read_cycle_analytics read_snippet read_project_member read_note read_cycle_analytics
read_pipeline read_build read_commit_status read_container_image read_pipeline read_build read_commit_status read_container_image
read_environment read_deployment read_merge_request read_pages read_environment read_deployment read_merge_request read_pages
...@@ -97,6 +97,100 @@ describe ProjectPolicy do ...@@ -97,6 +97,100 @@ describe ProjectPolicy do
end end
end end
context 'iterations' do
let(:current_user) { owner }
context 'when feature is disabled' do
before do
stub_licensed_features(iterations: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when feature is enabled' do
before do
stub_licensed_features(iterations: true)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
context 'when issues are disabled but merge requests are enabled' do
before do
project.update!(issues_enabled: false)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when issues are enabled but merge requests are enabled' do
before do
project.update!(merge_requests_enabled: false)
end
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when both issues and merge requests are disabled' do
before do
project.update!(issues_enabled: false, merge_requests_enabled: false)
end
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a developer' do
let(:current_user) { developer }
it { is_expected.to be_allowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is a guest' do
let(:current_user) { guest }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is not a member' do
let(:current_user) { build(:user) }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_allowed(:read_iteration) }
it { is_expected.to be_disallowed(:create_iteration, :admin_iteration) }
end
context 'when the project is private' do
let(:project) { create(:project, :private, namespace: owner.namespace) }
before do
project.add_maintainer(maintainer)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
context 'when user is not a member' do
let(:current_user) { build(:user) }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
context 'when user is logged out' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_iteration, :create_iteration, :admin_iteration) }
end
end
end
end
context 'issues feature' do context 'issues feature' do
subject { described_class.new(owner, project) } subject { described_class.new(owner, project) }
......
# frozen_string_literal: true
require 'spec_helper'
describe Iterations::CreateService do
shared_examples 'iterations create service' do
let_it_be(:user) { create(:user) }
before do
parent.add_developer(user)
end
context 'iterations feature enabled' do
before do
stub_licensed_features(iterations: true)
end
describe '#execute' do
let(:params) do
{
title: 'v2.1.9',
description: 'Patch release to fix security issue'
}
end
let(:response) { described_class.new(parent, user, params).execute }
let(:iteration) { response.payload[:iteration] }
let(:errors) { response.payload[:errors] }
context 'valid params' do
it 'creates an iteration' do
expect(response.success?).to be_truthy
expect(iteration).to be_persisted
expect(iteration.title).to eq('v2.1.9')
end
end
context 'invalid params' do
let(:params) do
{
description: 'Patch release to fix security issue'
}
end
it 'does not create an iteration but returns errors' do
expect(response.error?).to be_truthy
expect(errors.messages).to match({ title: ["can't be blank"] })
end
end
context 'no permissions' do
before do
parent.add_reporter(user)
end
it 'is not allowed' do
expect(response.error?).to be_truthy
expect(response.message).to eq('Operation not allowed')
end
end
end
end
context 'iterations feature disabled' do
before do
stub_licensed_features(iterations: false)
end
describe '#execute' do
let(:params) { { title: 'a' } }
let(:response) { described_class.new(parent, user, params).execute }
it 'is not allowed' do
expect(response.error?).to be_truthy
expect(response.message).to eq('Operation not allowed')
end
end
end
end
context 'for projects' do
let_it_be(:parent, refind: true) { create(:project) }
it_behaves_like 'iterations create service'
end
context 'for groups' do
let_it_be(:parent, refind: true) { create(:group) }
it_behaves_like 'iterations create service'
end
end
...@@ -8380,6 +8380,9 @@ msgstr "" ...@@ -8380,6 +8380,9 @@ msgstr ""
msgid "Error creating label." msgid "Error creating label."
msgstr "" msgstr ""
msgid "Error creating new iteration"
msgstr ""
msgid "Error deleting %{issuableType}" msgid "Error deleting %{issuableType}"
msgstr "" msgstr ""
...@@ -13738,6 +13741,9 @@ msgstr "" ...@@ -13738,6 +13741,9 @@ msgstr ""
msgid "New issue title" msgid "New issue title"
msgstr "" msgstr ""
msgid "New iteration created"
msgstr ""
msgid "New label" msgid "New label"
msgstr "" msgstr ""
...@@ -14421,6 +14427,9 @@ msgstr "" ...@@ -14421,6 +14427,9 @@ msgstr ""
msgid "Operation failed. Check pod logs for %{pod_name} for more details." msgid "Operation failed. Check pod logs for %{pod_name} for more details."
msgstr "" msgstr ""
msgid "Operation not allowed"
msgstr ""
msgid "Operation timed out. Check pod logs for %{pod_name} for more details." msgid "Operation timed out. Check pod logs for %{pod_name} for more details."
msgstr "" msgstr ""
......
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