Commit 0be510a4 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 6026bddc
......@@ -8,6 +8,7 @@
# Frontend maintainers should see everything in `app/assets/`
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill
/scripts/frontend/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill
# Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database
......
......@@ -20,6 +20,27 @@ $spacing-scale: (
5: #{4 * $grid-size}
);
/*
* Why another sizing scale???
* Great question, friend!
* This size scale is a "backport" of the equivalent set of "named" sizes
* (e.g. `xl` versus `70`) that came from the following design document as of 2019-10-23:
*
* https://gitlab-org.gitlab.io/gitlab-design/hosted/design-gitlab-specs/forms-spec-previews/
*
* (See `input-` items at the bottom)
*
* The presumption here is that these sizes will be standardized in GitLab UI and thus will be
* broadly useful here in the GitLab product when not using the GitLab UI components.
*/
$size-scale: (
'xs': #{10 * $grid-size},
's': #{20 * $grid-size},
'm': #{30 * $grid-size},
'l': #{40 * $grid-size},
'xl': #{70 * $grid-size}
);
/*
* Color schema
*/
......
......@@ -252,6 +252,7 @@
.fa-caret-down {
margin-left: 3px;
line-height: 0;
&.dropdown-btn-icon {
margin-left: 0;
......@@ -269,7 +270,7 @@
}
.count-badge,
.btn-xs {
.btn {
height: 24px;
}
......
......@@ -16,6 +16,12 @@
}
}
@each $index, $size in $size-scale {
#{'.mw-#{$index}'} {
max-width: $size;
}
}
.border-width-1px { border-width: 1px; }
.border-style-dashed { border-style: dashed; }
.border-style-solid { border-style: solid; }
......
......@@ -16,13 +16,8 @@ class NotificationSettingsController < ApplicationController
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
if params[:hide_label].present?
btn_class = params[:project_id].present? ? 'btn-xs' : ''
render_response("shared/notifications/_new_button", btn_class)
else
render_response
end
end
private
......@@ -42,7 +37,16 @@ class NotificationSettingsController < ApplicationController
can?(current_user, ability_name, resource)
end
def render_response(response_template = "shared/notifications/_button", btn_class = nil)
def render_response
btn_class = nil
if params[:hide_label].present?
btn_class = 'btn-xs' if params[:project_id].present?
response_template = 'shared/notifications/_new_button'
else
response_template = 'shared/notifications/_button'
end
render json: {
html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class),
saved: @saved
......
......@@ -8,23 +8,31 @@ module Types
present_using CommitPresenter
field :id, type: GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :sha, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :title, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :description, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :message, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :authored_date, type: Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :web_url, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :signature_html, type: GraphQL::STRING_TYPE,
null: true, calls_gitaly: true, description: 'Rendered html for the commit signature'
field :id, type: GraphQL::ID_TYPE, null: false,
description: 'ID (global ID) of the commit'
field :sha, type: GraphQL::STRING_TYPE, null: false,
description: 'SHA1 ID of the commit'
field :title, type: GraphQL::STRING_TYPE, null: true,
description: 'Title of the commit message'
field :description, type: GraphQL::STRING_TYPE, null: true,
description: 'Description of the commit message'
field :message, type: GraphQL::STRING_TYPE, null: true,
description: 'Raw commit message'
field :authored_date, type: Types::TimeType, null: true,
description: 'Timestamp of when the commit was authored'
field :web_url, type: GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the commit'
field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'Rendered HTML of the commit signature'
# models/commit lazy loads the author by email
field :author, type: Types::UserType, null: true # rubocop:disable Graphql/Descriptions
field :author, type: Types::UserType, null: true,
description: 'Author of the commit'
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
description: "Latest pipeline for this commit",
description: "Latest pipeline of the commit",
resolve: -> (obj, ctx, args) do
Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
end
......
......@@ -8,14 +8,17 @@ module Types
expose_permissions Types::PermissionTypes::Group
field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :web_url, GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the group'
field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do # rubocop:disable Graphql/Descriptions
field :avatar_url, GraphQL::STRING_TYPE, null: true,
description: 'Avatar URL of the group',
resolve: -> (group, args, ctx) do
group.avatar_url(only_path: false)
end
field :parent, GroupType, # rubocop:disable Graphql/Descriptions
null: true,
field :parent, GroupType, null: true,
description: 'Parent group',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
end
......
......@@ -12,53 +12,77 @@ module Types
present_using IssuePresenter
field :iid, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :iid, GraphQL::ID_TYPE, null: false,
description: "Internal ID of the issue"
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Title of the issue'
markdown_field :title_html, null: true
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the issue'
markdown_field :description_html, null: true
field :state, IssueStateEnum, null: false # rubocop:disable Graphql/Descriptions
field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference do # rubocop:disable Graphql/Descriptions
argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false # rubocop:disable Graphql/Descriptions
field :state, IssueStateEnum, null: false,
description: 'State of the issue'
field :reference, GraphQL::STRING_TYPE, null: false,
description: 'Internal reference of the issue. Returned in shortened format by default',
method: :to_reference do
argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false,
description: 'Boolean option specifying whether the reference should be returned in full'
end
field :author, Types::UserType, # rubocop:disable Graphql/Descriptions
null: false,
field :author, Types::UserType, null: false,
description: 'User that created the issue',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
# Remove complexity when BatchLoader is used
field :assignees, Types::UserType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions
field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
description: 'Assignees of the issue'
# Remove complexity when BatchLoader is used
field :labels, Types::LabelType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions
field :milestone, Types::MilestoneType, # rubocop:disable Graphql/Descriptions
null: true,
field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
description: 'Labels of the issue'
field :milestone, Types::MilestoneType, null: true,
description: 'Milestone of the issue',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :confidential, GraphQL::BOOLEAN_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :discussion_locked, GraphQL::BOOLEAN_TYPE, # rubocop:disable Graphql/Descriptions
null: false,
field :due_date, Types::TimeType, null: true,
description: 'Due date of the issue'
field :confidential, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates the issue is confidential'
field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates discussion is locked on the issue',
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :user_notes_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path # rubocop:disable Graphql/Descriptions
field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :relative_position, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'List of participants for the issue'
field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate on the issue'
field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the issue'
field :closed_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions
field :upvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of upvotes the issue has received'
field :downvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of downvotes the issue has received'
field :user_notes_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user notes of the issue'
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path,
description: 'Web path of the issue'
field :web_url, GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the issue'
field :relative_position, GraphQL::INT_TYPE, null: true,
description: 'Relative position of the issue (used for positioning in epic tree and issue boards)'
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
description: 'List of participants in the issue'
field :time_estimate, GraphQL::INT_TYPE, null: false,
description: 'Time estimate of the issue'
field :total_time_spent, GraphQL::INT_TYPE, null: false,
description: 'Total time reported as spent on the issue'
field :closed_at, Types::TimeType, null: true,
description: 'Timestamp of when the issue was closed'
field :created_at, Types::TimeType, null: false,
description: 'Timestamp of when the issue was created'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of when the issue was last updated'
field :task_completion_status, Types::TaskCompletionStatus, null: false,
description: 'Task completion status of the issue'
end
end
......
......@@ -6,10 +6,14 @@ module Types
authorize :read_label
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the label (markdown rendered as HTML for caching)'
markdown_field :description_html, null: true
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :text_color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Content of the label'
field :color, GraphQL::STRING_TYPE, null: false,
description: 'Background color of the label'
field :text_color, GraphQL::STRING_TYPE, null: false,
description: 'Text color of the label'
end
end
This diff is collapsed.
......@@ -6,7 +6,9 @@ module Types
authorize :read_instance_metadata
field :version, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :revision, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :version, GraphQL::STRING_TYPE, null: false,
description: 'Version'
field :revision, GraphQL::STRING_TYPE, null: false,
description: 'Revision'
end
end
......@@ -6,14 +6,21 @@ module Types
authorize :read_milestone
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :state, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the milestone'
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Title of the milestone'
field :state, GraphQL::STRING_TYPE, null: false,
description: 'State of the milestone'
field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :start_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :due_date, Types::TimeType, null: true,
description: 'Timestamp of the milestone due date'
field :start_date, Types::TimeType, null: true,
description: 'Timestamp of the milestone start date'
field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
field :created_at, Types::TimeType, null: false,
description: 'Timestamp of milestone creation'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of last milestone update'
end
end
......@@ -6,27 +6,35 @@ module Types
authorize :read_namespace
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the namespace'
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :full_name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Name of the namespace'
field :path, GraphQL::STRING_TYPE, null: false,
description: 'Path of the namespace'
field :full_name, GraphQL::STRING_TYPE, null: false,
description: 'Full name of the namespace'
field :full_path, GraphQL::ID_TYPE, null: false,
description: 'Full path of the namespace'
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description of the namespace'
markdown_field :description_html, null: true
field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled? # rubocop:disable Graphql/Descriptions
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :visibility, GraphQL::STRING_TYPE, null: true,
description: 'Visibility of the namespace'
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?,
description: 'Indicates if Large File Storage (LFS) is enabled for namespace'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if users can request access to namespace'
field :root_storage_statistics, Types::RootStorageStatisticsType,
null: true,
description: 'The aggregated storage statistics. Only available for root namespaces',
description: 'Aggregated storage statistics of the namespace. Only available for root namespaces',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find }
field :projects, # rubocop:disable Graphql/Descriptions
Types::ProjectType.connection_type,
null: false,
field :projects, Types::ProjectType.connection_type, null: false,
description: 'Projects within this namespace',
resolver: ::Resolvers::NamespaceProjectsResolver
end
end
......@@ -6,13 +6,20 @@ module Types
authorize :read_statistics
field :commit_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :commit_count, GraphQL::INT_TYPE, null: false,
description: 'Commit count of the project'
field :storage_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :repository_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :lfs_objects_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :build_artifacts_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :packages_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :wiki_size, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :storage_size, GraphQL::INT_TYPE, null: false,
description: 'Storage size of the project'
field :repository_size, GraphQL::INT_TYPE, null: false,
description: 'Repository size of the project'
field :lfs_objects_size, GraphQL::INT_TYPE, null: false,
description: 'Large File Storage (LFS) object size of the project'
field :build_artifacts_size, GraphQL::INT_TYPE, null: false,
description: 'Build artifacts size of the project'
field :packages_size, GraphQL::INT_TYPE, null: false,
description: 'Packages size of the project'
field :wiki_size, GraphQL::INT_TYPE, null: true,
description: 'Wiki size of the project'
end
end
This diff is collapsed.
......@@ -6,9 +6,13 @@ module Types
authorize :download_code
field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true # rubocop:disable Graphql/Descriptions
field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true # rubocop:disable Graphql/Descriptions
field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists? # rubocop:disable Graphql/Descriptions
field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true # rubocop:disable Graphql/Descriptions
field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'Default branch of the repository'
field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true,
description: 'Indicates repository has no visible content'
field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists?,
description: 'Indicates a corresponding Git repository exists on disk'
field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true,
description: 'Tree of the repository'
end
end
......@@ -8,8 +8,10 @@ module Types
graphql_name 'TaskCompletionStatus'
description 'Completion status of tasks'
field :count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :completed_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :count, GraphQL::INT_TYPE, null: false,
description: 'Number of total tasks'
field :completed_count, GraphQL::INT_TYPE, null: false,
description: 'Number of completed tasks'
end
# rubocop: enable Graphql/AuthorizeTypes
end
......@@ -8,12 +8,16 @@ module Types
present_using UserPresenter
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :username, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :avatar_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the user'
field :username, GraphQL::STRING_TYPE, null: false,
description: 'Username of the user. Unique within this instance of GitLab'
field :avatar_url, GraphQL::STRING_TYPE, null: false,
description: "URL of the user's avatar"
field :web_url, GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the user'
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
description: 'Todos of this user'
description: 'Todos of the user'
end
end
%h3.page-title
New Merge Request
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
%strong.ref-name= source_title
%span into
%strong.ref-name= target_title
%span.float-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f|
= render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter
= f.hidden_field :source_project_id
......
......@@ -2,5 +2,4 @@
%h3.page-title
Edit Merge Request #{@merge_request.to_reference}
%hr
= render 'form'
......@@ -12,6 +12,9 @@
= link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer'
and make sure your changes will not unintentionally remove theirs
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
%hr
.form-group.row
= form.label :title, class: 'col-form-label col-sm-2'
......@@ -34,8 +37,6 @@
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
= render 'shared/issuable/form/merge_params', issuable: issuable
= render 'shared/issuable/form/contribution', issuable: issuable, form: form
......
......@@ -4,21 +4,19 @@
- return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork?
%hr
- if issuable.new_record?
.form-group.row
= form.label :source_branch, class: 'col-form-label col-sm-2'
.col-sm-10
.issuable-form-select-holder
= form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 ref-name', disabled: true })
.form-group.row
= form.label :target_branch, class: 'col-form-label col-sm-2'
.col-sm-10.target-branch-select-dropdown-container
.issuable-form-select-holder
= form.hidden_field(:target_branch,
{ class: 'target_branch js-target-branch-select ref-name',
disabled: issuable.new_record?,
data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
- source_title, target_title = format_mr_branch_names(@merge_request)
.form-group.row.d-flex.gl-pl-3.gl-pr-3.branch-selector
.align-self-center
%span= s_('From %{source_title} into').html_safe % { source_title: "<code>#{source_title}</code>".html_safe }
- if issuable.new_record?
%code= target_title
&nbsp;
= link_to 'Change branches', mr_change_branches_path(issuable)
= link_to _('Change branches'), mr_change_branches_path(issuable)
- elsif issuable.for_fork?
%code= issuable.target_project_path + ":"
- unless issuable.new_record?
%span.dropdown.prepend-left-5.d-inline-block
= form.hidden_field(:target_branch,
{ class: 'target_branch js-target-branch-select ref-name mw-xl',
data: { placeholder: _('Select branch'), endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }})
......@@ -3,17 +3,17 @@
- return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork?
- if issuable.can_remove_source_branch?(current_user)
.form-group.row
.col-sm-10.offset-sm-2
.form-check
.form-group.row
.col-sm-2.col-form-label.pt-sm-0
%label
= _('Merge options')
.col-sm-10
- if issuable.can_remove_source_branch?(current_user)
.form-check.append-bottom-default
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
Delete source branch when merge request is accepted.
.form-group.row
.col-sm-10.offset-sm-2
.form-check
= hidden_field_tag 'merge_request[squash]', '0', id: nil
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
......
......@@ -17,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
%button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
%button.btn.dropdown-toggle.d-flex{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
......
- hide_label = local_assigns.fetch(:hide_label, false)
.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" }
.modal-dialog
.modal-content
......@@ -11,6 +13,7 @@
.container-fluid
= form_for notification_setting, html: { class: "custom-notifications-form" } do |f|
= hidden_setting_source_input(notification_setting)
= hidden_field_tag("hide_label", true) if hide_label
.row
.col-lg-4
%h4.prepend-top-0= _('Notification events')
......
......@@ -31,4 +31,4 @@
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
= content_for :scripts_body do
= render "shared/notifications/custom_notifications", notification_setting: notification_setting
= render "shared/notifications/custom_notifications", notification_setting: notification_setting, hide_label: true
---
title: Reduce new MR page redundancy by moving the source/target branch selector to
the top
merge_request: 17559
author:
type: changed
This diff is collapsed.
......@@ -93,8 +93,8 @@ module Gitlab
docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "",
qa: "~QA",
test: "~test for `spec/features/*`",
engineering_productivity: "Engineering Productivity for CI config review"
test: "~test ~Quality for `spec/features/*`",
engineering_productivity: '~"Engineering Productivity" for CI, Danger'
}.freeze
CATEGORIES = {
%r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
......@@ -104,7 +104,7 @@ module Gitlab
%r{\A(ee/)?public/} => :frontend,
%r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend,
%r{\Ascripts/frontend/} => :frontend,
%r{\A(ee/)?scripts/frontend/} => :frontend,
%r{(\A|/)(
\.babelrc |
\.eslintignore |
......@@ -130,14 +130,18 @@ module Gitlab
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
%r{\Arubocop/cop/migration(/|\.rb)} => :database,
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
%r{Dangerfile\z} => :engineering_productivity,
%r{\A(ee/)?(danger/|lib/gitlab/danger/)} => :engineering_productivity,
%r{\A(ee/)?scripts/} => :engineering_productivity,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
%r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend,
%r{\A(ee/)?spec/features/} => :test,
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
%r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
%r{\A(Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend,
%r{\A\.rubocop(_todo)?\.yml\z} => :backend,
......
......@@ -67,7 +67,10 @@ module Gitlab
area && labels.any?("devops::#{area.downcase}") if kind == :reviewer
when :engineering_productivity
role[/Engineering Productivity/] if kind == :reviewer
return false unless role[/Engineering Productivity/]
return true if kind == :reviewer
capabilities(project).include?("#{kind} backend")
else
capabilities(project).include?("#{kind} #{category}")
end
......
......@@ -2898,6 +2898,9 @@ msgstr ""
msgid "Change assignee(s)."
msgstr ""
msgid "Change branches"
msgstr ""
msgid "Change label"
msgstr ""
......@@ -7397,6 +7400,9 @@ msgstr ""
msgid "From %{providerTitle}"
msgstr ""
msgid "From %{source_title} into"
msgstr ""
msgid "From Bitbucket"
msgstr ""
......@@ -10287,6 +10293,9 @@ msgstr ""
msgid "Merge in progress"
msgstr ""
msgid "Merge options"
msgstr ""
msgid "Merge request"
msgstr ""
......@@ -14871,6 +14880,9 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
msgid "Select branch"
msgstr ""
msgid "Select branch/tag"
msgstr ""
......
......@@ -216,8 +216,7 @@ describe 'Dashboard Projects' do
expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq project_new_merge_request_path(project)
expect(find('#merge_request_target_project_id', visible: false).value).to eq project.id.to_s
expect(find('input#merge_request_source_branch', visible: false).value).to eq 'feature'
expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
expect(page).to have_content "From feature into master"
end
end
......
......@@ -17,7 +17,7 @@ describe 'User edits a merge request', :js do
end
it 'changes the target branch' do
expect(page).to have_content('Target branch')
expect(page).to have_content('From master into feature')
select2('merge-test', from: '#merge_request_target_branch')
click_button('Save changes')
......
......@@ -55,12 +55,16 @@ describe 'Cherry-pick Commits' do
end
end
context "I cherry-pick a commit in a new merge request" do
context "I cherry-pick a commit in a new merge request", :js do
it do
find('.header-action-buttons a.dropdown-toggle').click
find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do
click_button 'Cherry-pick'
end
wait_for_requests
expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.")
expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master")
end
......
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import actionComponent from '~/pipelines/components/graph/action_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import ActionComponent from '~/pipelines/components/graph/action_component.vue';
describe('pipeline graph action component', () => {
let component;
let wrapper;
let mock;
beforeEach(done => {
const ActionComponent = Vue.extend(actionComponent);
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onPost('foo.json').reply(200);
component = mountComponent(ActionComponent, {
wrapper = mount(ActionComponent, {
propsData: {
tooltipText: 'bar',
link: 'foo',
actionIcon: 'cancel',
},
sync: false,
});
Vue.nextTick(done);
});
afterEach(() => {
mock.restore();
component.$destroy();
wrapper.destroy();
});
it('should render the provided title as a bootstrap tooltip', () => {
expect(component.$el.getAttribute('data-original-title')).toEqual('bar');
expect(wrapper.attributes('data-original-title')).toBe('bar');
});
it('should update bootstrap tooltip when title changes', done => {
component.tooltipText = 'changed';
wrapper.setProps({ tooltipText: 'changed' });
component
wrapper.vm
.$nextTick()
.then(() => {
expect(component.$el.getAttribute('data-original-title')).toBe('changed');
expect(wrapper.attributes('data-original-title')).toBe('changed');
})
.then(done)
.catch(done.fail);
});
it('should render an svg', () => {
expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined();
expect(component.$el.querySelector('svg')).toBeDefined();
expect(wrapper.find('.ci-action-icon-wrapper')).toBeDefined();
expect(wrapper.find('svg')).toBeDefined();
});
describe('on click', () => {
it('emits `pipelineActionRequestComplete` after a successful request', done => {
spyOn(component, '$emit');
jest.spyOn(wrapper.vm, '$emit');
component.$el.click();
wrapper.find('button').trigger('click');
setTimeout(() => {
component
.$nextTick()
waitForPromises()
.then(() => {
expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
done();
})
.catch(done.fail);
done();
}, 0);
});
it('renders a loading icon while waiting for request', done => {
component.$el.click();
wrapper.find('button').trigger('click');
component.$nextTick(() => {
expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull();
setTimeout(() => {
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
done();
});
});
});
});
});
......@@ -17,6 +17,7 @@ describe('Pipelines Triggerer', () => {
const createComponent = () => {
wrapper = mount(pipelineTriggerer, {
propsData: mockData,
sync: false,
});
};
......@@ -49,6 +50,8 @@ describe('Pipelines Triggerer', () => {
},
});
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
});
});
});
import Vue from 'vue';
import tableRowComp from '~/pipelines/components/pipelines_table_row.vue';
import { mount } from '@vue/test-utils';
import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
const buildComponent = pipeline => {
const PipelinesTableRowComponent = Vue.extend(tableRowComp);
return new PipelinesTableRowComponent({
el: document.querySelector('.test-dom-element'),
const createWrapper = pipeline =>
mount(PipelinesTableRowComponent, {
propsData: {
pipeline,
autoDevopsHelpPath: 'foo',
viewType: 'root',
},
}).$mount();
};
sync: false,
});
let component;
let wrapper;
let pipeline;
let pipelineWithoutAuthor;
let pipelineWithoutCommit;
......@@ -32,28 +31,29 @@ describe('Pipelines Table Row', () => {
});
afterEach(() => {
component.$destroy();
wrapper.destroy();
wrapper = null;
});
it('should render a table row', () => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
expect(component.$el.getAttribute('class')).toContain('gl-responsive-table-row');
expect(wrapper.attributes('class')).toContain('gl-responsive-table-row');
});
describe('status column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
});
it('should render a pipeline link', () => {
expect(
component.$el.querySelector('.table-section.commit-link a').getAttribute('href'),
).toEqual(pipeline.path);
expect(wrapper.find('.table-section.commit-link a').attributes('href')).toEqual(
pipeline.path,
);
});
it('should render status text', () => {
expect(component.$el.querySelector('.table-section.commit-link a').textContent).toContain(
expect(wrapper.find('.table-section.commit-link a').text()).toContain(
pipeline.details.status.text,
);
});
......@@ -61,33 +61,32 @@ describe('Pipelines Table Row', () => {
describe('information column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
});
it('should render a pipeline link', () => {
expect(
component.$el.querySelector('.table-section:nth-child(2) a').getAttribute('href'),
).toEqual(pipeline.path);
expect(wrapper.find('.table-section:nth-child(2) a').attributes('href')).toEqual(
pipeline.path,
);
});
it('should render pipeline ID', () => {
expect(
component.$el.querySelector('.table-section:nth-child(2) a > span').textContent,
).toEqual(`#${pipeline.id}`);
expect(wrapper.find('.table-section:nth-child(2) a > span').text()).toEqual(
`#${pipeline.id}`,
);
});
describe('when a user is provided', () => {
it('should render user information', () => {
expect(
component.$el
.querySelector('.table-section:nth-child(3) .js-pipeline-url-user')
.getAttribute('href'),
wrapper.find('.table-section:nth-child(3) .js-pipeline-url-user').attributes('href'),
).toEqual(pipeline.user.path);
expect(
component.$el
.querySelector('.table-section:nth-child(3) .js-user-avatar-image-toolip')
.textContent.trim(),
wrapper
.find('.table-section:nth-child(3) .js-user-avatar-image-toolip')
.text()
.trim(),
).toEqual(pipeline.user.name);
});
});
......@@ -95,40 +94,47 @@ describe('Pipelines Table Row', () => {
describe('commit column', () => {
it('should render link to commit', () => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
const commitLink = component.$el.querySelector('.branch-commit .commit-sha');
const commitLink = wrapper.find('.branch-commit .commit-sha');
expect(commitLink.getAttribute('href')).toEqual(pipeline.commit.commit_path);
expect(commitLink.attributes('href')).toEqual(pipeline.commit.commit_path);
});
const findElements = () => {
const commitTitleElement = component.$el.querySelector('.branch-commit .commit-title');
const commitAuthorElement = commitTitleElement.querySelector('a.avatar-image-container');
const commitTitleElement = wrapper.find('.branch-commit .commit-title');
const commitAuthorElement = commitTitleElement.find('a.avatar-image-container');
if (!commitAuthorElement) {
return { commitAuthorElement };
if (!commitAuthorElement.exists()) {
return {
commitAuthorElement,
};
}
const commitAuthorLink = commitAuthorElement.getAttribute('href');
const commitAuthorLink = commitAuthorElement.attributes('href');
const commitAuthorName = commitAuthorElement
.querySelector('.js-user-avatar-image-toolip')
.textContent.trim();
return { commitAuthorElement, commitAuthorLink, commitAuthorName };
.find('.js-user-avatar-image-toolip')
.text()
.trim();
return {
commitAuthorElement,
commitAuthorLink,
commitAuthorName,
};
};
it('renders nothing without commit', () => {
expect(pipelineWithoutCommit.commit).toBe(null);
component = buildComponent(pipelineWithoutCommit);
wrapper = createWrapper(pipelineWithoutCommit);
const { commitAuthorElement } = findElements();
expect(commitAuthorElement).toBe(null);
expect(commitAuthorElement.exists()).toBe(false);
});
it('renders commit author', () => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(pipeline.commit.author.path);
......@@ -137,8 +143,8 @@ describe('Pipelines Table Row', () => {
it('renders commit with unregistered author', () => {
expect(pipelineWithoutAuthor.commit.author).toBe(null);
component = buildComponent(pipelineWithoutAuthor);
wrapper = createWrapper(pipelineWithoutAuthor);
const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`);
......@@ -148,13 +154,12 @@ describe('Pipelines Table Row', () => {
describe('stages column', () => {
beforeEach(() => {
component = buildComponent(pipeline);
wrapper = createWrapper(pipeline);
});
it('should render an icon for each stage', () => {
expect(
component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button')
.length,
wrapper.findAll('.table-section:nth-child(4) .js-builds-dropdown-button').length,
).toEqual(pipeline.details.stages.length);
});
});
......@@ -172,44 +177,49 @@ describe('Pipelines Table Row', () => {
withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry';
component = buildComponent(withActions);
wrapper = createWrapper(withActions);
});
it('should render the provided actions', () => {
expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull();
expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull();
const dropdownMenu = component.$el.querySelectorAll('.dropdown-menu');
expect(wrapper.find('.js-pipelines-retry-button').exists()).toBe(true);
expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true);
const dropdownMenu = wrapper.find('.dropdown-menu');
expect(dropdownMenu).toContainText(scheduledJobAction.name);
expect(dropdownMenu.text()).toContain(scheduledJobAction.name);
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', endpoint => {
expect(endpoint).toEqual('/retry');
expect(endpoint).toBe('/retry');
});
component.$el.querySelector('.js-pipelines-retry-button').click();
expect(component.isRetrying).toEqual(true);
wrapper.find('.js-pipelines-retry-button').trigger('click');
expect(wrapper.vm.isRetrying).toBe(true);
});
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$once('openConfirmationModal', data => {
const { id, ref, commit } = pipeline;
expect(data.endpoint).toEqual('/cancel');
expect(data.pipeline).toEqual(jasmine.objectContaining({ id, ref, commit }));
expect(data.endpoint).toBe('/cancel');
expect(data.pipeline).toEqual(
expect.objectContaining({
id,
ref,
commit,
}),
);
});
component.$el.querySelector('.js-pipelines-cancel-button').click();
wrapper.find('.js-pipelines-cancel-button').trigger('click');
});
it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => {
component.cancelingPipeline = pipeline.id;
component
wrapper.setProps({ cancelingPipeline: pipeline.id });
wrapper.vm
.$nextTick()
.then(() => {
expect(component.isCancelling).toEqual(true);
expect(wrapper.vm.isCancelling).toBe(true);
})
.then(done)
.catch(done.fail);
......
......@@ -178,6 +178,7 @@ describe Gitlab::Danger::Helper do
'app/assets/foo' | :frontend
'app/views/foo' | :frontend
'public/foo' | :frontend
'scripts/frontend/foo' | :frontend
'spec/javascripts/foo' | :frontend
'spec/frontend/bar' | :frontend
'vendor/assets/foo' | :frontend
......@@ -193,10 +194,8 @@ describe Gitlab::Danger::Helper do
'app/models/foo' | :backend
'bin/foo' | :backend
'config/foo' | :backend
'danger/foo' | :backend
'lib/foo' | :backend
'rubocop/foo' | :backend
'scripts/foo' | :backend
'spec/foo' | :backend
'spec/foo/bar' | :backend
......@@ -209,16 +208,24 @@ describe Gitlab::Danger::Helper do
'vendor/languages.yml' | :backend
'vendor/licenses.csv' | :backend
'Dangerfile' | :backend
'Gemfile' | :backend
'Gemfile.lock' | :backend
'Procfile' | :backend
'Rakefile' | :backend
'FOO_VERSION' | :backend
'Dangerfile' | :engineering_productivity
'danger/commit_messages/Dangerfile' | :engineering_productivity
'ee/danger/commit_messages/Dangerfile' | :engineering_productivity
'danger/commit_messages/' | :engineering_productivity
'ee/danger/commit_messages/' | :engineering_productivity
'.gitlab-ci.yml' | :engineering_productivity
'.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
'.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity
'scripts/foo' | :engineering_productivity
'lib/gitlab/danger/foo' | :engineering_productivity
'ee/lib/gitlab/danger/foo' | :engineering_productivity
'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
'ee/FOO_VERSION' | :unknown
......
......@@ -30,7 +30,7 @@ describe Gitlab::Danger::Teammate do
expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
end
context 'when labels contain Create and the category is test' do
context 'when labels contain devops::create and the category is test' do
let(:labels) { ['devops::create'] }
context 'when role is Test Automation Engineer, Create' do
......@@ -79,6 +79,22 @@ describe Gitlab::Danger::Teammate do
it '#maintainer? returns false' do
expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey
end
context 'when capabilities include maintainer backend' do
let(:capabilities) { ['maintainer backend'] }
it '#maintainer? returns true' do
expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy
end
end
context 'when capabilities include trainee_maintainer backend' do
let(:capabilities) { ['trainee_maintainer backend'] }
it '#traintainer? returns true' do
expect(subject.traintainer?(project, :engineering_productivity, labels)).to be_truthy
end
end
end
end
end
......
......@@ -262,7 +262,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'has the correct number of pipelines and statuses' do
expect(@project.ci_pipelines.size).to eq(6)
@project.ci_pipelines.zip([0, 2, 2, 2, 2, 2])
@project.ci_pipelines.order(:id).zip([2, 2, 2, 2, 2, 0])
.each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size)
end
......
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