Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-26 09:08:06 +00:00
parent 47a2a65f34
commit 27c6c4bf06
28 changed files with 929 additions and 326 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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')

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
#

View File

@ -179,6 +179,10 @@ class Milestone < ApplicationRecord
end
end
def subgroup_milestone?
group_milestone? && parent.subgroup?
end
private
def milestone_format_reference(format = :iid)

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Extract featurable concern from ProjectFeature
merge_request: 31700
author: Alexander Randa
type: other

View File

@ -0,0 +1,5 @@
---
title: Add mutation to create a merge request in GraphQL
merge_request: 31867
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix issuable listings with any label filter
merge_request: 31729
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix spelling error on Ci::RunnersFinder
merge_request: 32985
author: Arthur de Lapertosa Lisboa
type: fixed

View File

@ -6188,6 +6188,61 @@ type MergeRequestConnection {
pageInfo: PageInfo!
}
"""
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.
"""
@ -6673,11 +6728,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
"""
@ -6688,6 +6753,11 @@ type Milestone {
"""
state: MilestoneStateEnum!
"""
Indicates if milestone is at subgroup level
"""
subgroupMilestone: Boolean!
"""
Title of the milestone
"""
@ -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

View File

@ -17302,6 +17302,160 @@
"enumValues": null,
"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",
@ -18754,6 +18908,24 @@
"isDeprecated": false,
"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",
@ -18772,6 +18944,24 @@
"isDeprecated": false,
"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",
@ -18804,6 +18994,24 @@
"isDeprecated": false,
"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",
@ -19785,6 +19993,33 @@
"isDeprecated": false,
"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,

View File

@ -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 |

View File

@ -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] = {} }

View File

@ -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 ""

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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') }

View File

@ -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

View File

@ -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

View File

@ -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