Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-26 12:08:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-26 12:08:06 +0300
commit27c6c4bf061c3a2289ce4808b1b354535994d09d (patch)
treee973f10e40356141a4f19b94259b3b2f2b93b792
parent47a2a65f346719a0b4441a28a7adcbe747173f30 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss18
-rw-r--r--app/controllers/concerns/check_codeowner_rules.rb9
-rw-r--r--app/controllers/projects/blob_controller.rb17
-rw-r--r--app/finders/ci/runners_finder.rb4
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/graphql/mutations/merge_requests/create.rb63
-rw-r--r--app/graphql/types/milestone_type.rb12
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/models/concerns/featurable.rb99
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/project_feature.rb106
-rw-r--r--changelogs/unreleased/208412-featurable.yml5
-rw-r--r--changelogs/unreleased/215619_add_mutation_to_create_mr.yml5
-rw-r--r--changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml5
-rw-r--r--changelogs/unreleased/Fix-spelling-error.yml5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql71
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json235
-rw-r--r--doc/api/graphql/reference/index.md13
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb19
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb56
-rw-r--r--spec/graphql/mutations/merge_requests/create_spec.rb87
-rw-r--r--spec/models/concerns/featurable_spec.rb184
-rw-r--r--spec/models/concerns/issuable_spec.rb16
-rw-r--r--spec/models/milestone_spec.rb19
-rw-r--r--spec/models/project_feature_spec.rb131
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb51
28 files changed, 929 insertions, 326 deletions
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
index 61f971a3185..fd1108a7305 100644
--- a/app/assets/stylesheets/components/related_items_list.scss
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -23,6 +23,11 @@ $item-remove-button-space: 42px;
.sortable-link {
white-space: normal;
}
+
+ .item-assignees .avatar {
+ height: $gl-padding;
+ width: $gl-padding;
+ }
}
.item-body {
@@ -276,10 +281,6 @@ $item-remove-button-space: 42px;
/* Small devices (landscape phones, 768px and up) */
@include media-breakpoint-up(md) {
- .item-body .item-contents {
- max-width: 95%;
- }
-
.related-items-tree .item-contents,
.item-body .item-title {
max-width: 100%;
@@ -348,6 +349,11 @@ $item-remove-button-space: 42px;
}
.item-assignees {
+ .avatar {
+ height: $gl-padding-24;
+ width: $gl-padding-24;
+ }
+
.avatar-counter {
height: $gl-padding-24;
min-width: $gl-padding-24;
@@ -366,6 +372,10 @@ $item-remove-button-space: 42px;
.sortable-link {
line-height: 1.3;
}
+
+ .item-info-area {
+ flex-basis: auto;
+ }
}
@media only screen and (min-width: 1500px) {
diff --git a/app/controllers/concerns/check_codeowner_rules.rb b/app/controllers/concerns/check_codeowner_rules.rb
deleted file mode 100644
index 87947a356f7..00000000000
--- a/app/controllers/concerns/check_codeowner_rules.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module CheckCodeownerRules
- extend ActiveSupport::Concern
-
- def codeowners_check_error(project, branch_name, paths)
- ::Gitlab::CodeOwners::Validator.new(project, branch_name, paths).execute
- end
-end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index d044548e147..d35498260c6 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,7 +9,6 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
include RedirectsForMissingPathOnTree
include SourcegraphDecorator
- include CheckCodeownerRules
prepend_before_action :authenticate_user!, only: [:edit]
@@ -29,7 +28,6 @@ class Projects::BlobController < Projects::ApplicationController
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :validate_diff_params, only: :diff
before_action :set_last_commit_sha, only: [:edit, :update]
- before_action :validate_codeowner_rules, only: [:create, :update]
before_action only: :show do
push_frontend_feature_flag(:code_navigation, @project)
@@ -118,18 +116,7 @@ class Projects::BlobController < Projects::ApplicationController
private
- def validate_codeowner_rules
- return if params[:file_path].blank?
-
- codeowners_error = codeowners_check_error(@project, @branch_name, params[:file_path])
-
- if codeowners_error.present?
- flash.now[:alert] = codeowners_error
- view = params[:action] == 'update' ? :edit : :new
-
- render view
- end
- end
+ attr_reader :branch_name
def blob
@blob ||= @repository.blob_at(@commit.id, @path)
@@ -270,3 +257,5 @@ class Projects::BlobController < Projects::ApplicationController
params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent)
end
end
+
+Projects::BlobController.prepend_if_ee('EE::Projects::BlobController')
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 54d9d9522e0..1b76211c524 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -52,9 +52,9 @@ module Ci
raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group)
# Getting all runners from the group itself and all its descendants
- descentant_projects = Project.for_group_and_its_subgroups(@group)
+ descendant_projects = Project.for_group_and_its_subgroups(@group)
- @runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descentant_projects)
+ @runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
end
def filter_by_status!
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 7014f2ec205..5cdc22fd873 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -451,7 +451,7 @@ class IssuableFinder
if params.filter_by_no_label?
items.without_label
elsif params.filter_by_any_label?
- items.any_label
+ items.any_label(params[:sort])
else
items.with_label(params.label_names, params[:sort])
end
diff --git a/app/graphql/mutations/merge_requests/create.rb b/app/graphql/mutations/merge_requests/create.rb
new file mode 100644
index 00000000000..95d6fb100e7
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/create.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class Create < BaseMutation
+ include Mutations::ResolvesProject
+
+ graphql_name 'MergeRequestCreate'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Project full path the merge request is associated with'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :title)
+
+ argument :source_branch, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :source_branch)
+
+ argument :target_branch, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :target_branch)
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::MergeRequestType, :description)
+
+ field :merge_request,
+ Types::MergeRequestType,
+ null: true,
+ description: 'The merge request after mutation'
+
+ authorize :create_merge_request_from
+
+ def resolve(project_path:, title:, source_branch:, target_branch:, description: nil)
+ project = authorized_find!(full_path: project_path)
+
+ attributes = {
+ title: title,
+ source_branch: source_branch,
+ target_branch: target_branch,
+ author_id: current_user.id,
+ description: description
+ }
+
+ merge_request = ::MergeRequests::CreateService.new(project, current_user, attributes).execute
+
+ {
+ merge_request: merge_request.valid? ? merge_request : nil,
+ errors: errors_on_object(merge_request)
+ }
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index 900f8c6f01d..99bd6e819d6 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -35,5 +35,17 @@ module Types
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of last milestone update'
+
+ field :project_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at project level',
+ method: :project_milestone?
+
+ field :group_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at group level',
+ method: :group_milestone?
+
+ field :subgroup_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at subgroup level',
+ method: :subgroup_milestone?
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index aeff84b83b8..ae5469ba5b2 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -16,6 +16,7 @@ module Types
mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::Update
+ mount_mutation Mutations::MergeRequests::Create
mount_mutation Mutations::MergeRequests::SetLabels
mount_mutation Mutations::MergeRequests::SetLocked
mount_mutation Mutations::MergeRequests::SetMilestone
diff --git a/app/models/concerns/featurable.rb b/app/models/concerns/featurable.rb
new file mode 100644
index 00000000000..60aa46ce04c
--- /dev/null
+++ b/app/models/concerns/featurable.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+# == Featurable concern
+#
+# This concern adds features (tools) functionality to Project and Group
+# To enable features you need to call `set_available_features`
+#
+# Example:
+#
+# class ProjectFeature
+# include Featurable
+# set_available_features %i(wiki merge_request)
+
+module Featurable
+ extend ActiveSupport::Concern
+
+ # Can be enabled only for members, everyone or disabled
+ # Access control is made only for non private containers.
+ #
+ # Permission levels:
+ #
+ # Disabled: not enabled for anyone
+ # Private: enabled only for team members
+ # Enabled: enabled for everyone able to access the project
+ # Public: enabled for everyone (only allowed for pages)
+ DISABLED = 0
+ PRIVATE = 10
+ ENABLED = 20
+ PUBLIC = 30
+
+ STRING_OPTIONS = HashWithIndifferentAccess.new({
+ 'disabled' => DISABLED,
+ 'private' => PRIVATE,
+ 'enabled' => ENABLED,
+ 'public' => PUBLIC
+ }).freeze
+
+ class_methods do
+ def set_available_features(available_features = [])
+ @available_features = available_features
+
+ class_eval do
+ available_features.each do |feature|
+ define_method("#{feature}_enabled?") do
+ public_send("#{feature}_access_level") > DISABLED # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+ end
+
+ def available_features
+ @available_features
+ end
+
+ def access_level_attribute(feature)
+ feature = ensure_feature!(feature)
+
+ "#{feature}_access_level".to_sym
+ end
+
+ def quoted_access_level_column(feature)
+ attribute = connection.quote_column_name(access_level_attribute(feature))
+ table = connection.quote_table_name(table_name)
+
+ "#{table}.#{attribute}"
+ end
+
+ def access_level_from_str(level)
+ STRING_OPTIONS.fetch(level)
+ end
+
+ def str_from_access_level(level)
+ STRING_OPTIONS.key(level)
+ end
+
+ def ensure_feature!(feature)
+ feature = feature.model_name.plural if feature.respond_to?(:model_name)
+ feature = feature.to_sym
+ raise ArgumentError, "invalid feature: #{feature}" unless available_features.include?(feature)
+
+ feature
+ end
+ end
+
+ def access_level(feature)
+ public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def feature_available?(feature, user)
+ # This feature might not be behind a feature flag at all, so default to true
+ return false unless ::Feature.enabled?(feature, user, default_enabled: true)
+
+ get_permission(user, feature)
+ end
+
+ def string_access_level(feature)
+ self.class.str_from_access_level(access_level(feature))
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index a1b14dca4ac..1407a380978 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -139,7 +139,6 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :with_label_ids, ->(label_ids) { joins(:label_links).where(label_links: { label_id: label_ids }) }
- scope :any_label, -> { joins(:label_links).distinct }
scope :join_project, -> { joins(:project) }
scope :inc_notes_with_associations, -> { includes(notes: [:project, :author, :award_emoji]) }
scope :references_project, -> { references(:project) }
@@ -316,6 +315,14 @@ module Issuable
end
end
+ def any_label(sort = nil)
+ if sort
+ joins(:label_links).group(*grouping_columns(sort))
+ else
+ joins(:label_links).distinct
+ end
+ end
+
# Includes table keys in group by clause when sorting
# preventing errors in postgres
#
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index b5e4f62792e..ba9635897a3 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -179,6 +179,10 @@ class Milestone < ApplicationRecord
end
end
+ def subgroup_milestone?
+ group_milestone? && parent.subgroup?
+ end
+
private
def milestone_format_reference(format = :iid)
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 9201cd24d66..b3ebcbd4b17 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -1,51 +1,16 @@
# frozen_string_literal: true
class ProjectFeature < ApplicationRecord
- # == Project features permissions
- #
- # Grants access level to project tools
- #
- # Tools can be enabled only for users, everyone or disabled
- # Access control is made only for non private projects
- #
- # levels:
- #
- # Disabled: not enabled for anyone
- # Private: enabled only for team members
- # Enabled: enabled for everyone able to access the project
- # Public: enabled for everyone (only allowed for pages)
- #
-
- # Permission levels
- DISABLED = 0
- PRIVATE = 10
- ENABLED = 20
- PUBLIC = 30
+ include Featurable
FEATURES = %i(issues forking merge_requests wiki snippets builds repository pages metrics_dashboard).freeze
+
+ set_available_features(FEATURES)
+
PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER, metrics_dashboard: Gitlab::Access::REPORTER }.freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze
- STRING_OPTIONS = HashWithIndifferentAccess.new({
- 'disabled' => DISABLED,
- 'private' => PRIVATE,
- 'enabled' => ENABLED,
- 'public' => PUBLIC
- }).freeze
class << self
- def access_level_attribute(feature)
- feature = ensure_feature!(feature)
-
- "#{feature}_access_level".to_sym
- end
-
- def quoted_access_level_column(feature)
- attribute = connection.quote_column_name(access_level_attribute(feature))
- table = connection.quote_table_name(table_name)
-
- "#{table}.#{attribute}"
- end
-
def required_minimum_access_level(feature)
feature = ensure_feature!(feature)
@@ -60,24 +25,6 @@ class ProjectFeature < ApplicationRecord
required_minimum_access_level(feature)
end
end
-
- def access_level_from_str(level)
- STRING_OPTIONS.fetch(level)
- end
-
- def str_from_access_level(level)
- STRING_OPTIONS.key(level)
- end
-
- private
-
- def ensure_feature!(feature)
- feature = feature.model_name.plural if feature.respond_to?(:model_name)
- feature = feature.to_sym
- raise ArgumentError, "invalid project feature: #{feature}" unless FEATURES.include?(feature)
-
- feature
- end
end
# Default scopes force us to unscope here since a service may need to check
@@ -107,45 +54,6 @@ class ProjectFeature < ApplicationRecord
end
end
- def feature_available?(feature, user)
- # This feature might not be behind a feature flag at all, so default to true
- return false unless ::Feature.enabled?(feature, user, default_enabled: true)
-
- get_permission(user, feature)
- end
-
- def access_level(feature)
- public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def string_access_level(feature)
- ProjectFeature.str_from_access_level(access_level(feature))
- end
-
- def builds_enabled?
- builds_access_level > DISABLED
- end
-
- def wiki_enabled?
- wiki_access_level > DISABLED
- end
-
- def merge_requests_enabled?
- merge_requests_access_level > DISABLED
- end
-
- def forking_enabled?
- forking_access_level > DISABLED
- end
-
- def issues_enabled?
- issues_access_level > DISABLED
- end
-
- def pages_enabled?
- pages_access_level > DISABLED
- end
-
def public_pages?
return true unless Gitlab.config.pages.access_control
@@ -164,7 +72,7 @@ class ProjectFeature < ApplicationRecord
# which cannot be higher than repository access level
def repository_children_level
validator = lambda do |field|
- level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
+ level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
not_allowed = level > repository_access_level
self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
end
@@ -175,8 +83,8 @@ class ProjectFeature < ApplicationRecord
# Validates access level for other than pages cannot be PUBLIC
def allowed_access_levels
validator = lambda do |field|
- level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend
- not_allowed = level > ProjectFeature::ENABLED
+ level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
+ not_allowed = level > ENABLED
self.errors.add(field, "cannot have public visibility level") if not_allowed
end
diff --git a/changelogs/unreleased/208412-featurable.yml b/changelogs/unreleased/208412-featurable.yml
new file mode 100644
index 00000000000..02adcccec0b
--- /dev/null
+++ b/changelogs/unreleased/208412-featurable.yml
@@ -0,0 +1,5 @@
+---
+title: Extract featurable concern from ProjectFeature
+merge_request: 31700
+author: Alexander Randa
+type: other
diff --git a/changelogs/unreleased/215619_add_mutation_to_create_mr.yml b/changelogs/unreleased/215619_add_mutation_to_create_mr.yml
new file mode 100644
index 00000000000..477f8b60d5e
--- /dev/null
+++ b/changelogs/unreleased/215619_add_mutation_to_create_mr.yml
@@ -0,0 +1,5 @@
+---
+title: Add mutation to create a merge request in GraphQL
+merge_request: 31867
+author:
+type: added
diff --git a/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml b/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml
new file mode 100644
index 00000000000..25b0c302cf2
--- /dev/null
+++ b/changelogs/unreleased/216160-fix-label-any-with-custom-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issuable listings with any label filter
+merge_request: 31729
+author:
+type: fixed
diff --git a/changelogs/unreleased/Fix-spelling-error.yml b/changelogs/unreleased/Fix-spelling-error.yml
new file mode 100644
index 00000000000..c851da131fe
--- /dev/null
+++ b/changelogs/unreleased/Fix-spelling-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix spelling error on Ci::RunnersFinder
+merge_request: 32985
+author: Arthur de Lapertosa Lisboa
+type: fixed
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 7cfd4196a34..ccb9d5f0eb3 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -6189,6 +6189,61 @@ type MergeRequestConnection {
}
"""
+Autogenerated input type of MergeRequestCreate
+"""
+input MergeRequestCreateInput {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Description of the merge request (Markdown rendered as HTML for caching)
+ """
+ description: String
+
+ """
+ Project full path the merge request is associated with
+ """
+ projectPath: ID!
+
+ """
+ Source branch of the merge request
+ """
+ sourceBranch: String!
+
+ """
+ Target branch of the merge request
+ """
+ targetBranch: String!
+
+ """
+ Title of the merge request
+ """
+ title: String!
+}
+
+"""
+Autogenerated return type of MergeRequestCreate
+"""
+type MergeRequestCreatePayload {
+ """
+ A unique identifier for the client performing the mutation.
+ """
+ clientMutationId: String
+
+ """
+ Errors encountered during execution of the mutation.
+ """
+ errors: [String!]!
+
+ """
+ The merge request after mutation
+ """
+ mergeRequest: MergeRequest
+}
+
+"""
An edge in a connection.
"""
type MergeRequestEdge {
@@ -6674,11 +6729,21 @@ type Milestone {
dueDate: Time
"""
+ Indicates if milestone is at group level
+ """
+ groupMilestone: Boolean!
+
+ """
ID of the milestone
"""
id: ID!
"""
+ Indicates if milestone is at project level
+ """
+ projectMilestone: Boolean!
+
+ """
Timestamp of the milestone start date
"""
startDate: Time
@@ -6689,6 +6754,11 @@ type Milestone {
state: MilestoneStateEnum!
"""
+ Indicates if milestone is at subgroup level
+ """
+ subgroupMilestone: Boolean!
+
+ """
Title of the milestone
"""
title: String!
@@ -6788,6 +6858,7 @@ type Mutation {
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
markAsSpamSnippet(input: MarkAsSpamSnippetInput!): MarkAsSpamSnippetPayload
+ mergeRequestCreate(input: MergeRequestCreateInput!): MergeRequestCreatePayload
mergeRequestSetAssignees(input: MergeRequestSetAssigneesInput!): MergeRequestSetAssigneesPayload
mergeRequestSetLabels(input: MergeRequestSetLabelsInput!): MergeRequestSetLabelsPayload
mergeRequestSetLocked(input: MergeRequestSetLockedInput!): MergeRequestSetLockedPayload
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index db737743d32..ee383576bc3 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -17303,6 +17303,160 @@
"possibleTypes": null
},
{
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestCreateInput",
+ "description": "Autogenerated input type of MergeRequestCreate",
+ "fields": null,
+ "inputFields": [
+ {
+ "name": "projectPath",
+ "description": "Project full path the merge request is associated with",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "title",
+ "description": "Title of the merge request",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "sourceBranch",
+ "description": "Source branch of the merge request",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "targetBranch",
+ "description": "Target branch of the merge request",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "description",
+ "description": "Description of the merge request (Markdown rendered as HTML for caching)",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "MergeRequestCreatePayload",
+ "description": "Autogenerated return type of MergeRequestCreate",
+ "fields": [
+ {
+ "name": "clientMutationId",
+ "description": "A unique identifier for the client performing the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "errors",
+ "description": "Errors encountered during execution of the mutation.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "mergeRequest",
+ "description": "The merge request after mutation",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequest",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "MergeRequestEdge",
"description": "An edge in a connection.",
@@ -18755,6 +18909,24 @@
"deprecationReason": null
},
{
+ "name": "groupMilestone",
+ "description": "Indicates if milestone is at group level",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "id",
"description": "ID of the milestone",
"args": [
@@ -18773,6 +18945,24 @@
"deprecationReason": null
},
{
+ "name": "projectMilestone",
+ "description": "Indicates if milestone is at project level",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "startDate",
"description": "Timestamp of the milestone start date",
"args": [
@@ -18805,6 +18995,24 @@
"deprecationReason": null
},
{
+ "name": "subgroupMilestone",
+ "description": "Indicates if milestone is at subgroup level",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "Boolean",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "title",
"description": "Title of the milestone",
"args": [
@@ -19786,6 +19994,33 @@
"deprecationReason": null
},
{
+ "name": "mergeRequestCreate",
+ "description": null,
+ "args": [
+ {
+ "name": "input",
+ "description": null,
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "INPUT_OBJECT",
+ "name": "MergeRequestCreateInput",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "MergeRequestCreatePayload",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "mergeRequestSetAssignees",
"description": null,
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 7d0cf9eb322..add122d9f32 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -912,6 +912,16 @@ Autogenerated return type of MarkAsSpamSnippet
| `webUrl` | String | Web URL of the merge request |
| `workInProgress` | Boolean! | Indicates if the merge request is a work in progress (WIP) |
+## MergeRequestCreatePayload
+
+Autogenerated return type of MergeRequestCreate
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Errors encountered during execution of the mutation. |
+| `mergeRequest` | MergeRequest | The merge request after mutation |
+
## MergeRequestPermissions
Check permissions for the current user on a merge request
@@ -1019,9 +1029,12 @@ Represents a milestone.
| `createdAt` | Time! | Timestamp of milestone creation |
| `description` | String | Description of the milestone |
| `dueDate` | Time | Timestamp of the milestone due date |
+| `groupMilestone` | Boolean! | Indicates if milestone is at group level |
| `id` | ID! | ID of the milestone |
+| `projectMilestone` | Boolean! | Indicates if milestone is at project level |
| `startDate` | Time | Timestamp of the milestone start date |
| `state` | MilestoneStateEnum! | State of the milestone |
+| `subgroupMilestone` | Boolean! | Indicates if milestone is at subgroup level |
| `title` | String! | Title of the milestone |
| `updatedAt` | Time! | Timestamp of last milestone update |
| `webPath` | String! | Web path of the milestone |
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index 8159dcfed72..a4f1f2f11f9 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -54,6 +54,8 @@ module Banzai
doc
end
+ private
+
# Replace `JIRA-123` issue references in text with links to the referenced
# issue's details page.
#
@@ -63,21 +65,14 @@ module Banzai
# Returns a String with `JIRA-123` references replaced with links. All
# links have `gfm` and `gfm-issue` class names attached for styling.
def issue_link_filter(text, link_content: nil)
- project = context[:project]
-
self.class.references_in(text, issue_reference_pattern) do |match, id|
- ExternalIssue.new(id, project)
-
url = url_for_issue(id, project, only_path: context[:only_path])
-
- title = "Issue in #{project.external_issue_tracker.title}"
klass = reference_class(:issue)
data = data_attribute(project: project.id, external_issue: id)
-
content = link_content || match
%(<a href="#{url}" #{data}
- title="#{escape_once(title)}"
+ title="#{escape_once(issue_title)}"
class="#{klass}">#{content}</a>)
end
end
@@ -94,7 +89,13 @@ module Banzai
external_issues_cached(:external_issue_reference_pattern)
end
- private
+ def project
+ context[:project]
+ end
+
+ def issue_title
+ "Issue in #{project.external_issue_tracker.title}"
+ end
def external_issues_cached(attribute)
cached_attributes = Gitlab::SafeRequestStore[:banzai_external_issues_tracker_attributes] ||= Hash.new { |h, k| h[k] = {} }
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index cbc836cd21e..15a842fb32f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -10797,6 +10797,9 @@ msgstr ""
msgid "Group members"
msgstr ""
+msgid "Group milestone"
+msgstr ""
+
msgid "Group name"
msgstr ""
@@ -16628,6 +16631,9 @@ msgstr ""
msgid "Project members"
msgstr ""
+msgid "Project milestone"
+msgstr ""
+
msgid "Project name"
msgstr ""
@@ -20778,6 +20784,9 @@ msgstr ""
msgid "StorageSize|Unknown"
msgstr ""
+msgid "Subgroup milestone"
+msgstr ""
+
msgid "Subgroup overview"
msgstr ""
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 39b0cd4fb03..ad04c6e61e8 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -250,56 +250,6 @@ describe Projects::BlobController do
end
end
- shared_examples "file matches a codeowners rule" do
- let(:error_msg) { "Example error msg" }
-
- it "renders to the edit page with an error msg" do
- default_params[:file_path] = "CHANGELOG"
-
- expect_next_instance_of(Gitlab::CodeOwners::Validator) do |validator|
- expect(validator).to receive(:execute).and_return(error_msg)
- end
-
- subject
-
- expect(flash[:alert]).to eq(error_msg)
- expect(response).to render_template(expected_view)
- end
- end
-
- describe 'POST create' do
- let(:user) { create(:user) }
- let(:default_params) do
- {
- namespace_id: project.namespace,
- project_id: project,
- id: 'master',
- branch_name: 'master',
- file_name: 'CHANGELOG',
- content: 'Added changes',
- commit_message: 'Create CHANGELOG'
- }
- end
-
- before do
- project.add_developer(user)
-
- sign_in(user)
- end
-
- it 'redirects to blob' do
- post :create, params: default_params
-
- expect(response).to be_ok
- end
-
- it_behaves_like "file matches a codeowners rule" do
- subject { post :create, params: default_params }
-
- let(:expected_view) { :new }
- end
- end
-
describe 'PUT update' do
let(:user) { create(:user) }
let(:default_params) do
@@ -329,12 +279,6 @@ describe Projects::BlobController do
expect(response).to redirect_to(blob_after_edit_path)
end
- it_behaves_like "file matches a codeowners rule" do
- subject { put :update, params: default_params }
-
- let(:expected_view) { :edit }
- end
-
context '?from_merge_request_iid' do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:mr_params) { default_params.merge(from_merge_request_iid: merge_request.iid) }
diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb
new file mode 100644
index 00000000000..88acd3ed5b6
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/create_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::MergeRequests::Create do
+ subject(:mutation) { described_class.new(object: nil, context: context, field: nil) }
+
+ let_it_be(:project) { create(:project, :public, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:context) do
+ GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: { current_user: user },
+ object: nil
+ )
+ end
+
+ describe '#resolve' do
+ subject do
+ mutation.resolve(
+ project_path: project.full_path,
+ title: title,
+ source_branch: source_branch,
+ target_branch: target_branch,
+ description: description
+ )
+ end
+
+ let(:title) { 'MergeRequest' }
+ let(:source_branch) { 'feature' }
+ let(:target_branch) { 'master' }
+ let(:description) { nil }
+
+ let(:mutated_merge_request) { subject[:merge_request] }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have enough permissions to create a merge request' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user can create a merge request' do
+ before_all do
+ project.add_developer(user)
+ end
+
+ it 'creates a new merge request' do
+ expect { mutated_merge_request }.to change(MergeRequest, :count).by(1)
+ end
+
+ it 'returns a new merge request' do
+ expect(mutated_merge_request.title).to eq(title)
+ expect(subject[:errors]).to be_empty
+ end
+
+ context 'when optional description field is set' do
+ let(:description) { 'content' }
+
+ it 'returns a new merge request with a description' do
+ expect(mutated_merge_request.description).to eq(description)
+ expect(subject[:errors]).to be_empty
+ end
+ end
+
+ context 'when service cannot create a merge request' do
+ let(:title) { nil }
+
+ it 'does not create a new merge request' do
+ expect { mutated_merge_request }.not_to change(MergeRequest, :count)
+ end
+
+ it 'returns errors' do
+ expect(mutated_merge_request).to be_nil
+ expect(subject[:errors]).to eq(['Title can\'t be blank'])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/featurable_spec.rb b/spec/models/concerns/featurable_spec.rb
new file mode 100644
index 00000000000..89720e3652c
--- /dev/null
+++ b/spec/models/concerns/featurable_spec.rb
@@ -0,0 +1,184 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Featurable do
+ let_it_be(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:feature_class) { subject.class }
+ let(:features) { feature_class::FEATURES }
+
+ subject { project.project_feature }
+
+ describe '.quoted_access_level_column' do
+ it 'returns the table name and quoted column name for a feature' do
+ expected = '"project_features"."issues_access_level"'
+
+ expect(feature_class.quoted_access_level_column(:issues)).to eq(expected)
+ end
+ end
+
+ describe '.access_level_attribute' do
+ it { expect(feature_class.access_level_attribute(:wiki)).to eq :wiki_access_level }
+
+ it 'raises error for unspecified feature' do
+ expect { feature_class.access_level_attribute(:unknown) }
+ .to raise_error(ArgumentError, /invalid feature: unknown/)
+ end
+ end
+
+ describe '.set_available_features' do
+ let!(:klass) do
+ Class.new do
+ include Featurable
+ set_available_features %i(feature1 feature2)
+
+ def feature1_access_level
+ Featurable::DISABLED
+ end
+
+ def feature2_access_level
+ Featurable::ENABLED
+ end
+ end
+ end
+ let!(:instance) { klass.new }
+
+ it { expect(klass.available_features).to eq [:feature1, :feature2] }
+ it { expect(instance.feature1_enabled?).to be_falsey }
+ it { expect(instance.feature2_enabled?).to be_truthy }
+ end
+
+ describe '.available_features' do
+ it { expect(feature_class.available_features).to include(*features) }
+ end
+
+ describe '#access_level' do
+ it 'returns access level' do
+ expect(subject.access_level(:wiki)).to eq(subject.wiki_access_level)
+ end
+ end
+
+ describe '#feature_available?' do
+ let(:features) { %w(issues wiki builds merge_requests snippets repository pages metrics_dashboard) }
+
+ context 'when features are disabled' do
+ it "returns false" do
+ update_all_project_features(project, features, ProjectFeature::DISABLED)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
+ end
+ end
+ end
+
+ context 'when features are enabled only for team members' do
+ it "returns false when user is not a team member" do
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
+ end
+ end
+
+ it "returns true when user is a team member" do
+ project.add_developer(user)
+
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
+ end
+ end
+
+ it "returns true when user is a member of project group" do
+ group = create(:group)
+ project = create(:project, namespace: group)
+ group.add_developer(user)
+
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
+ end
+ end
+
+ context 'when admin mode is enabled', :enable_admin_mode do
+ it "returns true if user is an admin" do
+ user.update_attribute(:admin, true)
+
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
+ end
+ end
+ end
+
+ context 'when admin mode is disabled' do
+ it "returns false when user is an admin" do
+ user.update_attribute(:admin, true)
+
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
+ end
+ end
+ end
+ end
+
+ context 'when feature is enabled for everyone' do
+ it "returns true" do
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+
+ context 'when feature is disabled by a feature flag' do
+ it 'returns false' do
+ stub_feature_flags(issues: false)
+
+ expect(project.feature_available?(:issues, user)).to eq(false)
+ end
+ end
+
+ context 'when feature is enabled by a feature flag' do
+ it 'returns true' do
+ stub_feature_flags(issues: true)
+
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+ end
+
+ describe '#*_enabled?' do
+ let(:features) { %w(wiki builds merge_requests) }
+
+ it "returns false when feature is disabled" do
+ update_all_project_features(project, features, ProjectFeature::DISABLED)
+
+ features.each do |feature|
+ expect(project.public_send("#{feature}_enabled?")).to eq(false), "#{feature} failed"
+ end
+ end
+
+ it "returns true when feature is enabled only for team members" do
+ update_all_project_features(project, features, ProjectFeature::PRIVATE)
+
+ features.each do |feature|
+ expect(project.public_send("#{feature}_enabled?")).to eq(true), "#{feature} failed"
+ end
+ end
+
+ it "returns true when feature is enabled for everyone" do
+ features.each do |feature|
+ expect(project.public_send("#{feature}_enabled?")).to eq(true), "#{feature} failed"
+ end
+ end
+ end
+
+ def update_all_project_features(project, features, value)
+ project_feature_attributes = features.map { |f| ["#{f}_access_level", value] }.to_h
+ project.project_feature.update(project_feature_attributes)
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 59398e797c1..74ee7a87b7b 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -102,6 +102,22 @@ describe Issuable do
end
end
+ describe '.any_label' do
+ let_it_be(:issue_with_label) { create(:labeled_issue, labels: [create(:label)]) }
+ let_it_be(:issue_with_multiple_labels) { create(:labeled_issue, labels: [create(:label), create(:label)]) }
+ let_it_be(:issue_without_label) { create(:issue) }
+
+ it 'returns an issuable with at least one label' do
+ expect(issuable_class.any_label).to match_array([issue_with_label, issue_with_multiple_labels])
+ end
+
+ context 'for custom sorting' do
+ it 'returns an issuable with at least one label' do
+ expect(issuable_class.any_label('created_at')).to eq([issue_with_label, issue_with_multiple_labels])
+ end
+ end
+ end
+
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
let!(:searchable_issue2) { create(:issue, title: 'Aw') }
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 178fd5c04d0..06061bcc1a1 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -498,4 +498,23 @@ describe Milestone do
end
end
end
+
+ describe '#subgroup_milestone' do
+ context 'parent is subgroup' do
+ it 'returns true' do
+ group = create(:group)
+ subgroup = create(:group, :private, parent: group)
+
+ expect(build(:milestone, group: subgroup).subgroup_milestone?).to eq(true)
+ end
+ end
+
+ context 'parent is not subgroup' do
+ it 'returns false' do
+ group = create(:group)
+
+ expect(build(:milestone, group: group).subgroup_milestone?).to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index e072cc21b38..e33ea75bc5d 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -18,106 +18,6 @@ describe ProjectFeature do
end
end
- describe '.quoted_access_level_column' do
- it 'returns the table name and quoted column name for a feature' do
- expected = '"project_features"."issues_access_level"'
-
- expect(described_class.quoted_access_level_column(:issues)).to eq(expected)
- end
- end
-
- describe '#feature_available?' do
- let(:features) { %w(issues wiki builds merge_requests snippets repository pages metrics_dashboard) }
-
- context 'when features are disabled' do
- it "returns false" do
- update_all_project_features(project, features, ProjectFeature::DISABLED)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
- end
- end
- end
-
- context 'when features are enabled only for team members' do
- it "returns false when user is not a team member" do
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
- end
- end
-
- it "returns true when user is a team member" do
- project.add_developer(user)
-
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
- end
- end
-
- it "returns true when user is a member of project group" do
- group = create(:group)
- project = create(:project, namespace: group)
- group.add_developer(user)
-
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
- end
- end
-
- context 'when admin mode is enabled', :enable_admin_mode do
- it "returns true if user is an admin" do
- user.update_attribute(:admin, true)
-
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(true), "#{feature} failed"
- end
- end
- end
-
- context 'when admin mode is disabled' do
- it "returns false when user is an admin" do
- user.update_attribute(:admin, true)
-
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.feature_available?(feature.to_sym, user)).to eq(false), "#{feature} failed"
- end
- end
- end
- end
-
- context 'when feature is enabled for everyone' do
- it "returns true" do
- expect(project.feature_available?(:issues, user)).to eq(true)
- end
- end
-
- context 'when feature is disabled by a feature flag' do
- it 'returns false' do
- stub_feature_flags(issues: false)
-
- expect(project.feature_available?(:issues, user)).to eq(false)
- end
- end
-
- context 'when feature is enabled by a feature flag' do
- it 'returns true' do
- stub_feature_flags(issues: true)
-
- expect(project.feature_available?(:issues, user)).to eq(true)
- end
- end
- end
-
context 'repository related features' do
before do
project.project_feature.update(
@@ -153,32 +53,6 @@ describe ProjectFeature do
end
end
- describe '#*_enabled?' do
- let(:features) { %w(wiki builds merge_requests) }
-
- it "returns false when feature is disabled" do
- update_all_project_features(project, features, ProjectFeature::DISABLED)
-
- features.each do |feature|
- expect(project.public_send("#{feature}_enabled?")).to eq(false), "#{feature} failed"
- end
- end
-
- it "returns true when feature is enabled only for team members" do
- update_all_project_features(project, features, ProjectFeature::PRIVATE)
-
- features.each do |feature|
- expect(project.public_send("#{feature}_enabled?")).to eq(true), "#{feature} failed"
- end
- end
-
- it "returns true when feature is enabled for everyone" do
- features.each do |feature|
- expect(project.public_send("#{feature}_enabled?")).to eq(true), "#{feature} failed"
- end
- end
- end
-
describe 'default pages access level' do
subject { project_feature.pages_access_level }
@@ -313,9 +187,4 @@ describe ProjectFeature do
expect(described_class.required_minimum_access_level_for_private_project(:issues)).to eq(Gitlab::Access::GUEST)
end
end
-
- def update_all_project_features(project, features, value)
- project_feature_attributes = features.map { |f| ["#{f}_access_level", value] }.to_h
- project.project_feature.update(project_feature_attributes)
- end
end
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
new file mode 100644
index 00000000000..5c63f655f1d
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Creation of a new merge request' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let(:input) do
+ {
+ project_path: project.full_path,
+ title: title,
+ source_branch: source_branch,
+ target_branch: target_branch
+ }
+ end
+ let(:title) { 'MergeRequest' }
+ let(:source_branch) { 'new_branch' }
+ let(:target_branch) { 'master' }
+
+ let(:mutation) { graphql_mutation(:merge_request_create, input) }
+ let(:mutation_response) { graphql_mutation_response(:merge_request_create) }
+
+ context 'the user is not allowed to create a branch' do
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+
+ context 'when user has permissions to create a merge request' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'creates a new merge request' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['mergeRequest']).to include(
+ 'title' => title
+ )
+ end
+
+ context 'when source branch is equal to the target branch' do
+ let(:source_branch) { target_branch }
+
+ it_behaves_like 'a mutation that returns errors in the response',
+ errors: ['Branch conflict You can\'t use same project/branch for source and target']
+ end
+ end
+end