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

Add latest changes from gitlab-org/gitlab@master

parent 6026bddc
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
# Frontend maintainers should see everything in `app/assets/` # Frontend maintainers should see everything in `app/assets/`
app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill
*.scss @annabeldunstone @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/` # Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database db/ @gitlab-org/maintainers/database
......
...@@ -20,6 +20,27 @@ $spacing-scale: ( ...@@ -20,6 +20,27 @@ $spacing-scale: (
5: #{4 * $grid-size} 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 * Color schema
*/ */
......
...@@ -252,6 +252,7 @@ ...@@ -252,6 +252,7 @@
.fa-caret-down { .fa-caret-down {
margin-left: 3px; margin-left: 3px;
line-height: 0;
&.dropdown-btn-icon { &.dropdown-btn-icon {
margin-left: 0; margin-left: 0;
...@@ -269,7 +270,7 @@ ...@@ -269,7 +270,7 @@
} }
.count-badge, .count-badge,
.btn-xs { .btn {
height: 24px; height: 24px;
} }
......
...@@ -16,6 +16,12 @@ ...@@ -16,6 +16,12 @@
} }
} }
@each $index, $size in $size-scale {
#{'.mw-#{$index}'} {
max-width: $size;
}
}
.border-width-1px { border-width: 1px; } .border-width-1px { border-width: 1px; }
.border-style-dashed { border-style: dashed; } .border-style-dashed { border-style: dashed; }
.border-style-solid { border-style: solid; } .border-style-solid { border-style: solid; }
......
...@@ -16,12 +16,7 @@ class NotificationSettingsController < ApplicationController ...@@ -16,12 +16,7 @@ class NotificationSettingsController < ApplicationController
@notification_setting = current_user.notification_settings.find(params[:id]) @notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source)) @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
if params[:hide_label].present? render_response
btn_class = params[:project_id].present? ? 'btn-xs' : ''
render_response("shared/notifications/_new_button", btn_class)
else
render_response
end
end end
private private
...@@ -42,7 +37,16 @@ class NotificationSettingsController < ApplicationController ...@@ -42,7 +37,16 @@ class NotificationSettingsController < ApplicationController
can?(current_user, ability_name, resource) can?(current_user, ability_name, resource)
end 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: { render json: {
html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class), html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class),
saved: @saved saved: @saved
......
...@@ -8,23 +8,31 @@ module Types ...@@ -8,23 +8,31 @@ module Types
present_using CommitPresenter present_using CommitPresenter
field :id, type: GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions field :id, type: GraphQL::ID_TYPE, null: false,
field :sha, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'ID (global ID) of the commit'
field :title, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :sha, type: GraphQL::STRING_TYPE, null: false,
field :description, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'SHA1 ID of the commit'
field :message, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :title, type: GraphQL::STRING_TYPE, null: true,
field :authored_date, type: Types::TimeType, null: true # rubocop:disable Graphql/Descriptions description: 'Title of the commit message'
field :web_url, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :description, type: GraphQL::STRING_TYPE, null: true,
field :signature_html, type: GraphQL::STRING_TYPE, description: 'Description of the commit message'
null: true, calls_gitaly: true, description: 'Rendered html for the commit signature' 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 # 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, field :latest_pipeline,
type: Types::Ci::PipelineType, type: Types::Ci::PipelineType,
null: true, null: true,
description: "Latest pipeline for this commit", description: "Latest pipeline of the commit",
resolve: -> (obj, ctx, args) do resolve: -> (obj, ctx, args) do
Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
end end
......
...@@ -8,14 +8,17 @@ module Types ...@@ -8,14 +8,17 @@ module Types
expose_permissions Types::PermissionTypes::Group 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,
group.avatar_url(only_path: false) description: 'Avatar URL of the group',
end resolve: -> (group, args, ctx) do
group.avatar_url(only_path: false)
end
field :parent, GroupType, # rubocop:disable Graphql/Descriptions field :parent, GroupType, null: true,
null: true, description: 'Parent group',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end end
end end
......
...@@ -12,53 +12,77 @@ module Types ...@@ -12,53 +12,77 @@ module Types
present_using IssuePresenter present_using IssuePresenter
field :iid, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions field :iid, GraphQL::ID_TYPE, null: false,
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: "Internal ID of the issue"
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Title of the issue'
markdown_field :title_html, null: true 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 markdown_field :description_html, null: true
field :state, IssueStateEnum, null: false # rubocop:disable Graphql/Descriptions field :state, IssueStateEnum, null: false,
description: 'State of the issue'
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 :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 end
field :author, Types::UserType, # rubocop:disable Graphql/Descriptions field :author, Types::UserType, null: false,
null: false, description: 'User that created the issue',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find }
# Remove complexity when BatchLoader is used # 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 # Remove complexity when BatchLoader is used
field :labels, Types::LabelType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
field :milestone, Types::MilestoneType, # rubocop:disable Graphql/Descriptions description: 'Labels of the issue'
null: true, 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 } 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 :due_date, Types::TimeType, null: true,
field :confidential, GraphQL::BOOLEAN_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Due date of the issue'
field :discussion_locked, GraphQL::BOOLEAN_TYPE, # rubocop:disable Graphql/Descriptions field :confidential, GraphQL::BOOLEAN_TYPE, null: false,
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 } resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :upvotes, GraphQL::INT_TYPE, null: false,
field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Number of upvotes the issue has received'
field :user_notes_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :downvotes, GraphQL::INT_TYPE, null: false,
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path # rubocop:disable Graphql/Descriptions description: 'Number of downvotes the issue has received'
field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :user_notes_count, GraphQL::INT_TYPE, null: false,
field :relative_position, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Number of user notes of the issue'
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path,
field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'List of participants for the issue' description: 'Web path of the issue'
field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate on the issue' field :web_url, GraphQL::STRING_TYPE, null: false,
field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the issue' description: 'Web URL of the issue'
field :relative_position, GraphQL::INT_TYPE, null: true,
field :closed_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions description: 'Relative position of the issue (used for positioning in epic tree and issue boards)'
field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions field :participants, Types::UserType.connection_type, null: true, complexity: 5,
field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions description: 'List of participants in the issue'
field :time_estimate, GraphQL::INT_TYPE, null: false,
field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions 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
end end
......
...@@ -6,10 +6,14 @@ module Types ...@@ -6,10 +6,14 @@ module Types
authorize :read_label 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 markdown_field :description_html, null: true
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :title, GraphQL::STRING_TYPE, null: false,
field :color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Content of the label'
field :text_color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions 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
end end
...@@ -12,70 +12,116 @@ module Types ...@@ -12,70 +12,116 @@ module Types
present_using MergeRequestPresenter present_using MergeRequestPresenter
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions field :id, GraphQL::ID_TYPE, null: false,
field :iid, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'ID of the merge request'
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :iid, GraphQL::STRING_TYPE, null: false,
description: 'Internal ID of the merge request'
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Title of the merge request'
markdown_field :title_html, null: true 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 merge request (markdown rendered as HTML for caching)'
markdown_field :description_html, null: true markdown_field :description_html, null: true
field :state, MergeRequestStateEnum, null: false # rubocop:disable Graphql/Descriptions field :state, MergeRequestStateEnum, null: false,
field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions description: 'State of the merge request'
field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions field :created_at, Types::TimeType, null: false,
field :source_project, Types::ProjectType, null: true # rubocop:disable Graphql/Descriptions description: 'Timestamp of when the merge request was created'
field :target_project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions field :updated_at, Types::TimeType, null: false,
field :diff_refs, Types::DiffRefsType, null: true # rubocop:disable Graphql/Descriptions description: 'Timestamp of when the merge request was last updated'
# Alias for target_project field :source_project, Types::ProjectType, null: true,
field :project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions description: 'Source project of the merge request'
field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id # rubocop:disable Graphql/Descriptions field :target_project, Types::ProjectType, null: false,
field :source_project_id, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Target project of the merge request'
field :target_project_id, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :diff_refs, Types::DiffRefsType, null: true,
field :source_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'References of the base SHA, the head SHA, and the start SHA for this merge request'
field :target_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :project, Types::ProjectType, null: false,
field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false # rubocop:disable Graphql/Descriptions description: 'Alias for target_project'
field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id,
field :diff_head_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'ID of the merge request project'
field :merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :source_project_id, GraphQL::INT_TYPE, null: true,
field :user_notes_count, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'ID of the merge request source project'
field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions field :target_project_id, GraphQL::INT_TYPE, null: false,
field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions description: 'ID of the merge request target project'
field :merge_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :source_branch, GraphQL::STRING_TYPE, null: false,
field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Source branch of the merge request'
field :merge_error, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :target_branch, GraphQL::STRING_TYPE, null: false,
field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Target branch of the merge request'
field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false # rubocop:disable Graphql/Descriptions field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false,
field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Indicates if the merge request is a work in progress (WIP)'
field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true # rubocop:disable Graphql/Descriptions field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
# rubocop:disable Graphql/Descriptions description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)'
field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage" field :diff_head_sha, GraphQL::STRING_TYPE, null: true,
# rubocop:enable Graphql/Descriptions description: 'Diff head SHA of the merge request'
field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :merge_commit_sha, GraphQL::STRING_TYPE, null: true,
field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false # rubocop:disable Graphql/Descriptions description: 'SHA of the merge request commit (set once merged)'
field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false # rubocop:disable Graphql/Descriptions field :user_notes_count, GraphQL::INT_TYPE, null: true,
field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'User notes count of the merge request'
field :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true,
field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Indicates if the source branch of the merge request will be deleted after merge'
field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true,
description: 'Indicates if the project settings will lead to source branch deletion after merge'
field :merge_status, GraphQL::STRING_TYPE, null: true,
description: 'Status of the merge request'
field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true,
description: 'Commit SHA of the merge request if merge is in progress'
field :merge_error, GraphQL::STRING_TYPE, null: true,
description: 'Error message due to a merge error'
field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if members of the target project can push to the fork'
field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false,
description: 'Indicates if the merge request will be rebased'
field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true,
description: 'Rebase commit SHA of the merge request'
field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true,
description: 'Indicates if there is a rebase currently in progress for the merge request'
field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage",
description: 'Deprecated - renamed to defaultMergeCommitMessage'
field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true,
description: 'Default merge commit message of the merge request'
field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false,
description: 'Indicates if a merge is currently occurring'
field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false,
description: 'Indicates if the source branch of the merge request exists'
field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged'
field :web_url, GraphQL::STRING_TYPE, null: true,
description: 'Web URL of the merge request'
field :upvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of upvotes for the merge request'
field :downvotes, GraphQL::INT_TYPE, null: false,
description: 'Number of downvotes for the merge request'
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline # rubocop:disable Graphql/Descriptions field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline,
field :pipelines, Types::Ci::PipelineType.connection_type, # rubocop:disable Graphql/Descriptions description: 'The pipeline running on the branch HEAD of the merge request'
field :pipelines, Types::Ci::PipelineType.connection_type,
description: 'Pipelines for the merge request',
resolver: Resolvers::MergeRequestPipelinesResolver resolver: Resolvers::MergeRequestPipelinesResolver
field :milestone, Types::MilestoneType, description: 'The milestone this merge request is linked to', field :milestone, Types::MilestoneType, null: true,
null: true, description: 'The milestone of the merge request',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :assignees, Types::UserType.connection_type, null: true, complexity: 5, description: 'The list of assignees for the merge request' field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'The list of participants on the merge request' description: 'Assignees of the merge request'
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
description: 'Participants in the merge request'
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5, field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
description: 'Boolean flag for whether the currently logged in user is subscribed to this MR' description: 'Indicates if the currently logged in user is subscribed to this merge request'
field :labels, Types::LabelType.connection_type, null: true, complexity: 5, description: 'The list of labels on the merge request' field :labels, Types::LabelType.connection_type, null: true, complexity: 5,
field :discussion_locked, GraphQL::BOOLEAN_TYPE, description: 'Boolean flag determining if comments on the merge request are locked to members only', description: 'Labels of the merge request'
field :discussion_locked, GraphQL::BOOLEAN_TYPE,
description: 'Indicates if comments on the merge request are locked to members only',
null: false, null: false,
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked } resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate for the merge request' field :time_estimate, GraphQL::INT_TYPE, null: false,
field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the merge request' description: 'Time estimate of the merge request'
field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference, description: 'Internal merge request reference. Returned in shortened format by default' do field :total_time_spent, GraphQL::INT_TYPE, null: false,
argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false, description: 'Boolean option specifying whether the reference should be returned in full' description: 'Total time reported as spent on the merge request'
field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference,
description: 'Internal reference of the merge request. Returned in shortened format by default' do
argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false,
description: 'Boolean option specifying whether the reference should be returned in full'
end end
field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions field :task_completion_status, Types::TaskCompletionStatus, null: false,
description: Types::TaskCompletionStatus.description
end end
end end
...@@ -6,7 +6,9 @@ module Types ...@@ -6,7 +6,9 @@ module Types
authorize :read_instance_metadata authorize :read_instance_metadata
field :version, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :version, GraphQL::STRING_TYPE, null: false,
field :revision, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Version'
field :revision, GraphQL::STRING_TYPE, null: false,
description: 'Revision'
end end
end end
...@@ -6,14 +6,21 @@ module Types ...@@ -6,14 +6,21 @@ module Types
authorize :read_milestone authorize :read_milestone
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :description, GraphQL::STRING_TYPE, null: true,
field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Description of the milestone'
field :state, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions 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 :due_date, Types::TimeType, null: true,
field :start_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions 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 :created_at, Types::TimeType, null: false,
field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions description: 'Timestamp of milestone creation'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of last milestone update'
end end
end end
...@@ -6,27 +6,35 @@ module Types ...@@ -6,27 +6,35 @@ module Types
authorize :read_namespace 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 :name, GraphQL::STRING_TYPE, null: false,
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Name of the namespace'
field :full_name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :path, GraphQL::STRING_TYPE, null: false,
field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions 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 markdown_field :description_html, null: true
field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :visibility, GraphQL::STRING_TYPE, null: true,
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled? # rubocop:disable Graphql/Descriptions description: 'Visibility of the namespace'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions 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, field :root_storage_statistics, Types::RootStorageStatisticsType,
null: true, 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 } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find }
field :projects, # rubocop:disable Graphql/Descriptions field :projects, Types::ProjectType.connection_type, null: false,
Types::ProjectType.connection_type, description: 'Projects within this namespace',
null: false,
resolver: ::Resolvers::NamespaceProjectsResolver resolver: ::Resolvers::NamespaceProjectsResolver
end end
end end
...@@ -6,13 +6,20 @@ module Types ...@@ -6,13 +6,20 @@ module Types
authorize :read_statistics 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 :storage_size, GraphQL::INT_TYPE, null: false,
field :repository_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Storage size of the project'
field :lfs_objects_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :repository_size, GraphQL::INT_TYPE, null: false,
field :build_artifacts_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Repository size of the project'
field :packages_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :lfs_objects_size, GraphQL::INT_TYPE, null: false,
field :wiki_size, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions 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
end end
...@@ -8,98 +8,142 @@ module Types ...@@ -8,98 +8,142 @@ module Types
expose_permissions Types::PermissionTypes::Project expose_permissions Types::PermissionTypes::Project
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the project'
field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions field :full_path, GraphQL::ID_TYPE, null: false,
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Full path of the project'
field :path, GraphQL::STRING_TYPE, null: false,
description: 'Path of the project'
field :name_with_namespace, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :name_with_namespace, GraphQL::STRING_TYPE, null: false,
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Full name of the project with its namespace'
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Name of the project (without namespace)'
field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :description, GraphQL::STRING_TYPE, null: true,
description: 'Short description of the project'
markdown_field :description_html, null: true markdown_field :description_html, null: true
field :tag_list, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :tag_list, GraphQL::STRING_TYPE, null: true,
description: 'List of project tags'
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :http_url_to_repo, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true,
field :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'URL to connect to the project via SSH'
field :http_url_to_repo, GraphQL::STRING_TYPE, null: true,
field :star_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'URL to connect to the project via HTTPS'
field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true # 4 times # rubocop:disable Graphql/Descriptions field :web_url, GraphQL::STRING_TYPE, null: true,
description: 'Web URL of the project'
field :created_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions
field :last_activity_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions field :star_count, GraphQL::INT_TYPE, null: false,
description: 'Number of times the project has been starred'
field :archived, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true, # 4 times
description: 'Number of times the project has been forked'
field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :created_at, Types::TimeType, null: true,
field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Timestamp of the project creation'
field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :last_activity_at, Types::TimeType, null: true,
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Timestamp of the project last activity'
field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :archived, GraphQL::BOOLEAN_TYPE, null: true,
field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions description: 'Archived status of the project'
project.avatar_url(only_path: false)
end field :visibility, GraphQL::STRING_TYPE, null: true,
description: 'Visibility of the project'
field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if the project stores Docker container images in a container registry'
field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if shared runners are enabled on the project'
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if the project has Large File Storage (LFS) enabled'
field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.'
field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'URL to avatar image file of the project',
resolve: -> (project, args, ctx) do
project.avatar_url(only_path: false)
end
%i[issues merge_requests wiki snippets].each do |feature| %i[issues merge_requests wiki snippets].each do |feature|
field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true,
project.feature_available?(feature, ctx[:current_user]) description: "(deprecated) Does this project have #{feature} enabled?. Use `#{feature}_access_level` instead",
end resolve: -> (project, args, ctx) do
project.feature_available?(feature, ctx[:current_user])
end
end end
field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true,
project.feature_available?(:builds, ctx[:current_user]) description: '(deprecated) Enable jobs for this project. Use `builds_access_level` instead',
end resolve: -> (project, args, ctx) do
project.feature_available?(:builds, ctx[:current_user])
field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true # rubocop:disable Graphql/Descriptions end
field :open_issues_count, GraphQL::INT_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true,
project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts'
end
field :open_issues_count, GraphQL::INT_TYPE, null: true,
field :import_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions description: 'Number of open issues for the project',
resolve: -> (project, args, ctx) do
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions project.open_issues_count if project.feature_available?(:issues, ctx[:current_user])
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions end
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions field :import_status, GraphQL::STRING_TYPE, null: true,
field :remove_source_branch_after_merge, GraphQL::BOOLEAN_TYPE, null: true, description: 'Remove the source branch by default after merge' description: 'Status of project import background job of the project'
field :namespace, Types::NamespaceType, null: true # rubocop:disable Graphql/Descriptions field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
field :group, Types::GroupType, null: true # rubocop:disable Graphql/Descriptions description: 'Indicates if merge requests of the project can only be merged with successful jobs'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
field :statistics, Types::ProjectStatisticsType, # rubocop:disable Graphql/Descriptions description: 'Indicates if users can request member access to the project'
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if merge requests of the project can only be merged when all the discussions are resolved'
field :printing_merge_request_link_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line'
field :remove_source_branch_after_merge, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project'
field :namespace, Types::NamespaceType, null: true,
description: 'Namespace of the project'
field :group, Types::GroupType, null: true,
description: 'Group of the project'
field :statistics, Types::ProjectStatisticsType,
null: true, null: true,
description: 'Statistics of the project',
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find } resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
field :repository, Types::RepositoryType, null: true # rubocop:disable Graphql/Descriptions field :repository, Types::RepositoryType, null: true,
description: 'Git repository of the project'
field :merge_requests, # rubocop:disable Graphql/Descriptions field :merge_requests,
Types::MergeRequestType.connection_type, Types::MergeRequestType.connection_type,
null: true, null: true,
description: 'Merge requests of the project',
resolver: Resolvers::MergeRequestsResolver resolver: Resolvers::MergeRequestsResolver
field :merge_request, # rubocop:disable Graphql/Descriptions field :merge_request,
Types::MergeRequestType, Types::MergeRequestType,
null: true, null: true,
description: 'A single merge request of the project',
resolver: Resolvers::MergeRequestsResolver.single resolver: Resolvers::MergeRequestsResolver.single
field :issues, # rubocop:disable Graphql/Descriptions field :issues,
Types::IssueType.connection_type, Types::IssueType.connection_type,
null: true, null: true,
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver resolver: Resolvers::IssuesResolver
field :issue, # rubocop:disable Graphql/Descriptions field :issue,
Types::ExtendedIssueType, Types::ExtendedIssueType,
null: true, null: true,
description: 'A single issue of the project',
resolver: Resolvers::IssuesResolver.single resolver: Resolvers::IssuesResolver.single
field :pipelines, # rubocop:disable Graphql/Descriptions field :pipelines,
Types::Ci::PipelineType.connection_type, Types::Ci::PipelineType.connection_type,
null: true, null: true,
description: 'Build pipelines of the project',
resolver: Resolvers::ProjectPipelinesResolver resolver: Resolvers::ProjectPipelinesResolver
end end
end end
...@@ -6,9 +6,13 @@ module Types ...@@ -6,9 +6,13 @@ module Types
authorize :download_code authorize :download_code
field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true # rubocop:disable Graphql/Descriptions field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true # rubocop:disable Graphql/Descriptions description: 'Default branch of the repository'
field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists? # rubocop:disable Graphql/Descriptions field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true,
field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true # rubocop:disable Graphql/Descriptions 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
end end
...@@ -8,8 +8,10 @@ module Types ...@@ -8,8 +8,10 @@ module Types
graphql_name 'TaskCompletionStatus' graphql_name 'TaskCompletionStatus'
description 'Completion status of tasks' description 'Completion status of tasks'
field :count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions field :count, GraphQL::INT_TYPE, null: false,
field :completed_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Number of total tasks'
field :completed_count, GraphQL::INT_TYPE, null: false,
description: 'Number of completed tasks'
end end
# rubocop: enable Graphql/AuthorizeTypes # rubocop: enable Graphql/AuthorizeTypes
end end
...@@ -8,12 +8,16 @@ module Types ...@@ -8,12 +8,16 @@ module Types
present_using UserPresenter present_using UserPresenter
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :name, GraphQL::STRING_TYPE, null: false,
field :username, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions description: 'Human-readable name of the user'
field :avatar_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions field :username, GraphQL::STRING_TYPE, null: false,
field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions 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, field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver, resolver: Resolvers::TodoResolver,
description: 'Todos of this user' description: 'Todos of the user'
end end
end end
%h3.page-title %h3.page-title
New Merge Request 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| = 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 = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
......
...@@ -2,5 +2,4 @@ ...@@ -2,5 +2,4 @@
%h3.page-title %h3.page-title
Edit Merge Request #{@merge_request.to_reference} Edit Merge Request #{@merge_request.to_reference}
%hr
= render 'form' = render 'form'
...@@ -12,6 +12,9 @@ ...@@ -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' = 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 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-group.row
= form.label :title, class: 'col-form-label col-sm-2' = form.label :title, class: 'col-form-label col-sm-2'
...@@ -34,8 +37,6 @@ ...@@ -34,8 +37,6 @@
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form = 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/merge_params', issuable: issuable
= render 'shared/issuable/form/contribution', issuable: issuable, form: form = render 'shared/issuable/form/contribution', issuable: issuable, form: form
......
...@@ -4,21 +4,19 @@ ...@@ -4,21 +4,19 @@
- return unless issuable.is_a?(MergeRequest) - return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork? - return if issuable.closed_without_fork?
%hr - source_title, target_title = format_mr_branch_names(@merge_request)
- if issuable.new_record?
.form-group.row .form-group.row.d-flex.gl-pl-3.gl-pr-3.branch-selector
= form.label :source_branch, class: 'col-form-label col-sm-2' .align-self-center
.col-sm-10 %span= s_('From %{source_title} into').html_safe % { source_title: "<code>#{source_title}</code>".html_safe }
.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') }})
- if issuable.new_record? - if issuable.new_record?
%code= target_title
&nbsp; &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 @@ ...@@ -3,17 +3,17 @@
- return unless issuable.is_a?(MergeRequest) - return unless issuable.is_a?(MergeRequest)
- return if issuable.closed_without_fork? - return if issuable.closed_without_fork?
- if issuable.can_remove_source_branch?(current_user) .form-group.row
.form-group.row .col-sm-2.col-form-label.pt-sm-0
.col-sm-10.offset-sm-2 %label
.form-check = _('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 = 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' = 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 = label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
Delete source branch when merge request is accepted. Delete source branch when merge request is accepted.
.form-group.row
.col-sm-10.offset-sm-2
.form-check .form-check
= hidden_field_tag 'merge_request[squash]', '0', id: nil = hidden_field_tag 'merge_request[squash]', '0', id: nil
= check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input' = check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input'
......
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
.js-notification-toggle-btns .js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) } %div{ class: ("btn-group" if notification_setting.custom?) }
- 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") = icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level) = 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') = icon('caret-down')
.sr-only Toggle dropdown .sr-only Toggle dropdown
- else - 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 .float-left
= icon("bell", class: "js-notification-loading") = icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level) = 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.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" }
.modal-dialog .modal-dialog
.modal-content .modal-content
...@@ -11,6 +13,7 @@ ...@@ -11,6 +13,7 @@
.container-fluid .container-fluid
= form_for notification_setting, html: { class: "custom-notifications-form" } do |f| = form_for notification_setting, html: { class: "custom-notifications-form" } do |f|
= hidden_setting_source_input(notification_setting) = hidden_setting_source_input(notification_setting)
= hidden_field_tag("hide_label", true) if hide_label
.row .row
.col-lg-4 .col-lg-4
%h4.prepend-top-0= _('Notification events') %h4.prepend-top-0= _('Notification events')
......
...@@ -31,4 +31,4 @@ ...@@ -31,4 +31,4 @@
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting = render "shared/notifications/notification_dropdown", notification_setting: notification_setting
= content_for :scripts_body do = 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
...@@ -47,16 +47,16 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -47,16 +47,16 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | | | `id` | ID! | ID (global ID) of the commit |
| `sha` | String! | | | `sha` | String! | SHA1 ID of the commit |
| `title` | String | | | `title` | String | Title of the commit message |
| `description` | String | | | `description` | String | Description of the commit message |
| `message` | String | | | `message` | String | Raw commit message |
| `authoredDate` | Time | | | `authoredDate` | Time | Timestamp of when the commit was authored |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the commit |
| `signatureHtml` | String | Rendered html for the commit signature | | `signatureHtml` | String | Rendered HTML of the commit signature |
| `author` | User | | | `author` | User | Author of the commit |
| `latestPipeline` | Pipeline | Latest pipeline for this commit | | `latestPipeline` | Pipeline | Latest pipeline of the commit |
### CreateDiffNotePayload ### CreateDiffNotePayload
...@@ -226,30 +226,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -226,30 +226,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
| `iid` | ID! | | | `iid` | ID! | Internal ID of the issue |
| `title` | String! | | | `title` | String! | Title of the issue |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` | | `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `description` | String | | | `description` | String | Description of the issue |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `state` | IssueState! | | | `state` | IssueState! | State of the issue |
| `reference` | String! | | | `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `author` | User! | | | `author` | User! | User that created the issue |
| `milestone` | Milestone | | | `milestone` | Milestone | Milestone of the issue |
| `dueDate` | Time | | | `dueDate` | Time | Due date of the issue |
| `confidential` | Boolean! | | | `confidential` | Boolean! | Indicates the issue is confidential |
| `discussionLocked` | Boolean! | | | `discussionLocked` | Boolean! | Indicates discussion is locked on the issue |
| `upvotes` | Int! | | | `upvotes` | Int! | Number of upvotes the issue has received |
| `downvotes` | Int! | | | `downvotes` | Int! | Number of downvotes the issue has received |
| `userNotesCount` | Int! | | | `userNotesCount` | Int! | Number of user notes of the issue |
| `webPath` | String! | | | `webPath` | String! | Web path of the issue |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the issue |
| `relativePosition` | Int | | | `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
| `timeEstimate` | Int! | The time estimate on the issue | | `timeEstimate` | Int! | Time estimate of the issue |
| `totalTimeSpent` | Int! | Total time reported as spent on the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `closedAt` | Time | | | `closedAt` | Time | Timestamp of when the issue was closed |
| `createdAt` | Time! | | | `createdAt` | Time! | Timestamp of when the issue was created |
| `updatedAt` | Time! | | | `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `taskCompletionStatus` | TaskCompletionStatus! | | | `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
| `epic` | Epic | The epic to which issue belongs | | `epic` | Epic | The epic to which issue belongs |
| `weight` | Int | | | `weight` | Int | |
| `designs` | DesignCollection | | | `designs` | DesignCollection | |
...@@ -283,30 +283,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -283,30 +283,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
| `iid` | ID! | | | `iid` | ID! | Internal ID of the issue |
| `title` | String! | | | `title` | String! | Title of the issue |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` | | `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `description` | String | | | `description` | String | Description of the issue |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `state` | IssueState! | | | `state` | IssueState! | State of the issue |
| `reference` | String! | | | `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `author` | User! | | | `author` | User! | User that created the issue |
| `milestone` | Milestone | | | `milestone` | Milestone | Milestone of the issue |
| `dueDate` | Time | | | `dueDate` | Time | Due date of the issue |
| `confidential` | Boolean! | | | `confidential` | Boolean! | Indicates the issue is confidential |
| `discussionLocked` | Boolean! | | | `discussionLocked` | Boolean! | Indicates discussion is locked on the issue |
| `upvotes` | Int! | | | `upvotes` | Int! | Number of upvotes the issue has received |
| `downvotes` | Int! | | | `downvotes` | Int! | Number of downvotes the issue has received |
| `userNotesCount` | Int! | | | `userNotesCount` | Int! | Number of user notes of the issue |
| `webPath` | String! | | | `webPath` | String! | Web path of the issue |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the issue |
| `relativePosition` | Int | | | `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
| `timeEstimate` | Int! | The time estimate on the issue | | `timeEstimate` | Int! | Time estimate of the issue |
| `totalTimeSpent` | Int! | Total time reported as spent on the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `closedAt` | Time | | | `closedAt` | Time | Timestamp of when the issue was closed |
| `createdAt` | Time! | | | `createdAt` | Time! | Timestamp of when the issue was created |
| `updatedAt` | Time! | | | `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `taskCompletionStatus` | TaskCompletionStatus! | | | `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
| `epic` | Epic | The epic to which issue belongs | | `epic` | Epic | The epic to which issue belongs |
| `weight` | Int | | | `weight` | Int | |
| `designs` | DesignCollection | | | `designs` | DesignCollection | |
...@@ -317,21 +317,21 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -317,21 +317,21 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | | | `id` | ID! | ID of the namespace |
| `name` | String! | | | `name` | String! | Name of the namespace |
| `path` | String! | | | `path` | String! | Path of the namespace |
| `fullName` | String! | | | `fullName` | String! | Full name of the namespace |
| `fullPath` | ID! | | | `fullPath` | ID! | Full path of the namespace |
| `description` | String | | | `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `visibility` | String | | | `visibility` | String | Visibility of the namespace |
| `lfsEnabled` | Boolean | | | `lfsEnabled` | Boolean | Indicates if Large File Storage (LFS) is enabled for namespace |
| `requestAccessEnabled` | Boolean | | | `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace |
| `rootStorageStatistics` | RootStorageStatistics | The aggregated storage statistics. Only available for root namespaces | | `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource | | `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the group |
| `avatarUrl` | String | | | `avatarUrl` | String | Avatar URL of the group |
| `parent` | Group | | | `parent` | Group | Parent group |
| `epicsEnabled` | Boolean | | | `epicsEnabled` | Boolean | |
| `epic` | Epic | | | `epic` | Epic | |
...@@ -346,30 +346,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -346,30 +346,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
| `iid` | ID! | | | `iid` | ID! | Internal ID of the issue |
| `title` | String! | | | `title` | String! | Title of the issue |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` | | `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `description` | String | | | `description` | String | Description of the issue |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `state` | IssueState! | | | `state` | IssueState! | State of the issue |
| `reference` | String! | | | `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `author` | User! | | | `author` | User! | User that created the issue |
| `milestone` | Milestone | | | `milestone` | Milestone | Milestone of the issue |
| `dueDate` | Time | | | `dueDate` | Time | Due date of the issue |
| `confidential` | Boolean! | | | `confidential` | Boolean! | Indicates the issue is confidential |
| `discussionLocked` | Boolean! | | | `discussionLocked` | Boolean! | Indicates discussion is locked on the issue |
| `upvotes` | Int! | | | `upvotes` | Int! | Number of upvotes the issue has received |
| `downvotes` | Int! | | | `downvotes` | Int! | Number of downvotes the issue has received |
| `userNotesCount` | Int! | | | `userNotesCount` | Int! | Number of user notes of the issue |
| `webPath` | String! | | | `webPath` | String! | Web path of the issue |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the issue |
| `relativePosition` | Int | | | `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
| `timeEstimate` | Int! | The time estimate on the issue | | `timeEstimate` | Int! | Time estimate of the issue |
| `totalTimeSpent` | Int! | Total time reported as spent on the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `closedAt` | Time | | | `closedAt` | Time | Timestamp of when the issue was closed |
| `createdAt` | Time! | | | `createdAt` | Time! | Timestamp of when the issue was created |
| `updatedAt` | Time! | | | `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `taskCompletionStatus` | TaskCompletionStatus! | | | `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
| `epic` | Epic | The epic to which issue belongs | | `epic` | Epic | The epic to which issue belongs |
| `weight` | Int | | | `weight` | Int | |
| `designs` | DesignCollection | | | `designs` | DesignCollection | |
...@@ -392,65 +392,65 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -392,65 +392,65 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `description` | String | | | `description` | String | Description of the label (markdown rendered as HTML for caching) |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `title` | String! | | | `title` | String! | Content of the label |
| `color` | String! | | | `color` | String! | Background color of the label |
| `textColor` | String! | | | `textColor` | String! | Text color of the label |
### MergeRequest ### MergeRequest
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | MergeRequestPermissions! | Permissions for the current user on the resource | | `userPermissions` | MergeRequestPermissions! | Permissions for the current user on the resource |
| `id` | ID! | | | `id` | ID! | ID of the merge request |
| `iid` | String! | | | `iid` | String! | Internal ID of the merge request |
| `title` | String! | | | `title` | String! | Title of the merge request |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` | | `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
| `description` | String | | | `description` | String | Description of the merge request (markdown rendered as HTML for caching) |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `state` | MergeRequestState! | | | `state` | MergeRequestState! | State of the merge request |
| `createdAt` | Time! | | | `createdAt` | Time! | Timestamp of when the merge request was created |
| `updatedAt` | Time! | | | `updatedAt` | Time! | Timestamp of when the merge request was last updated |
| `sourceProject` | Project | | | `sourceProject` | Project | Source project of the merge request |
| `targetProject` | Project! | | | `targetProject` | Project! | Target project of the merge request |
| `diffRefs` | DiffRefs | | | `diffRefs` | DiffRefs | References of the base SHA, the head SHA, and the start SHA for this merge request |
| `project` | Project! | | | `project` | Project! | Alias for target_project |
| `projectId` | Int! | | | `projectId` | Int! | ID of the merge request project |
| `sourceProjectId` | Int | | | `sourceProjectId` | Int | ID of the merge request source project |
| `targetProjectId` | Int! | | | `targetProjectId` | Int! | ID of the merge request target project |
| `sourceBranch` | String! | | | `sourceBranch` | String! | Source branch of the merge request |
| `targetBranch` | String! | | | `targetBranch` | String! | Target branch of the merge request |
| `workInProgress` | Boolean! | | | `workInProgress` | Boolean! | Indicates if the merge request is a work in progress (WIP) |
| `mergeWhenPipelineSucceeds` | Boolean | | | `mergeWhenPipelineSucceeds` | Boolean | Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS) |
| `diffHeadSha` | String | | | `diffHeadSha` | String | Diff head SHA of the merge request |
| `mergeCommitSha` | String | | | `mergeCommitSha` | String | SHA of the merge request commit (set once merged) |
| `userNotesCount` | Int | | | `userNotesCount` | Int | User notes count of the merge request |
| `shouldRemoveSourceBranch` | Boolean | | | `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge |
| `forceRemoveSourceBranch` | Boolean | | | `forceRemoveSourceBranch` | Boolean | Indicates if the project settings will lead to source branch deletion after merge |
| `mergeStatus` | String | | | `mergeStatus` | String | Status of the merge request |
| `inProgressMergeCommitSha` | String | | | `inProgressMergeCommitSha` | String | Commit SHA of the merge request if merge is in progress |
| `mergeError` | String | | | `mergeError` | String | Error message due to a merge error |
| `allowCollaboration` | Boolean | | | `allowCollaboration` | Boolean | Indicates if members of the target project can push to the fork |
| `shouldBeRebased` | Boolean! | | | `shouldBeRebased` | Boolean! | Indicates if the merge request will be rebased |
| `rebaseCommitSha` | String | | | `rebaseCommitSha` | String | Rebase commit SHA of the merge request |
| `rebaseInProgress` | Boolean! | | | `rebaseInProgress` | Boolean! | Indicates if there is a rebase currently in progress for the merge request |
| `mergeCommitMessage` | String | | | `mergeCommitMessage` | String | Deprecated - renamed to defaultMergeCommitMessage |
| `defaultMergeCommitMessage` | String | | | `defaultMergeCommitMessage` | String | Default merge commit message of the merge request |
| `mergeOngoing` | Boolean! | | | `mergeOngoing` | Boolean! | Indicates if a merge is currently occurring |
| `sourceBranchExists` | Boolean! | | | `sourceBranchExists` | Boolean! | Indicates if the source branch of the merge request exists |
| `mergeableDiscussionsState` | Boolean | | | `mergeableDiscussionsState` | Boolean | Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged |
| `webUrl` | String | | | `webUrl` | String | Web URL of the merge request |
| `upvotes` | Int! | | | `upvotes` | Int! | Number of upvotes for the merge request |
| `downvotes` | Int! | | | `downvotes` | Int! | Number of downvotes for the merge request |
| `headPipeline` | Pipeline | | | `headPipeline` | Pipeline | The pipeline running on the branch HEAD of the merge request |
| `milestone` | Milestone | The milestone this merge request is linked to | | `milestone` | Milestone | The milestone of the merge request |
| `subscribed` | Boolean! | Boolean flag for whether the currently logged in user is subscribed to this MR | | `subscribed` | Boolean! | Indicates if the currently logged in user is subscribed to this merge request |
| `discussionLocked` | Boolean! | Boolean flag determining if comments on the merge request are locked to members only | | `discussionLocked` | Boolean! | Indicates if comments on the merge request are locked to members only |
| `timeEstimate` | Int! | The time estimate for the merge request | | `timeEstimate` | Int! | Time estimate of the merge request |
| `totalTimeSpent` | Int! | Total time reported as spent on the merge request | | `totalTimeSpent` | Int! | Total time reported as spent on the merge request |
| `reference` | String! | Internal merge request reference. Returned in shortened format by default | | `reference` | String! | Internal reference of the merge request. Returned in shortened format by default |
| `taskCompletionStatus` | TaskCompletionStatus! | | | `taskCompletionStatus` | TaskCompletionStatus! | Completion status of tasks |
### MergeRequestPermissions ### MergeRequestPermissions
...@@ -477,36 +477,36 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -477,36 +477,36 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `version` | String! | | | `version` | String! | Version |
| `revision` | String! | | | `revision` | String! | Revision |
### Milestone ### Milestone
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `description` | String | | | `description` | String | Description of the milestone |
| `title` | String! | | | `title` | String! | Title of the milestone |
| `state` | String! | | | `state` | String! | State of the milestone |
| `dueDate` | Time | | | `dueDate` | Time | Timestamp of the milestone due date |
| `startDate` | Time | | | `startDate` | Time | Timestamp of the milestone start date |
| `createdAt` | Time! | | | `createdAt` | Time! | Timestamp of milestone creation |
| `updatedAt` | Time! | | | `updatedAt` | Time! | Timestamp of last milestone update |
### Namespace ### Namespace
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `id` | ID! | | | `id` | ID! | ID of the namespace |
| `name` | String! | | | `name` | String! | Name of the namespace |
| `path` | String! | | | `path` | String! | Path of the namespace |
| `fullName` | String! | | | `fullName` | String! | Full name of the namespace |
| `fullPath` | ID! | | | `fullPath` | ID! | Full path of the namespace |
| `description` | String | | | `description` | String | Description of the namespace |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `visibility` | String | | | `visibility` | String | Visibility of the namespace |
| `lfsEnabled` | Boolean | | | `lfsEnabled` | Boolean | Indicates if Large File Storage (LFS) is enabled for namespace |
| `requestAccessEnabled` | Boolean | | | `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace |
| `rootStorageStatistics` | RootStorageStatistics | The aggregated storage statistics. Only available for root namespaces | | `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
### Note ### Note
...@@ -578,47 +578,47 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -578,47 +578,47 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource | | `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
| `id` | ID! | | | `id` | ID! | ID of the project |
| `fullPath` | ID! | | | `fullPath` | ID! | Full path of the project |
| `path` | String! | | | `path` | String! | Path of the project |
| `nameWithNamespace` | String! | | | `nameWithNamespace` | String! | Full name of the project with its namespace |
| `name` | String! | | | `name` | String! | Name of the project (without namespace) |
| `description` | String | | | `description` | String | Short description of the project |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `tagList` | String | | | `tagList` | String | List of project tags |
| `sshUrlToRepo` | String | | | `sshUrlToRepo` | String | URL to connect to the project via SSH |
| `httpUrlToRepo` | String | | | `httpUrlToRepo` | String | URL to connect to the project via HTTPS |
| `webUrl` | String | | | `webUrl` | String | Web URL of the project |
| `starCount` | Int! | | | `starCount` | Int! | Number of times the project has been starred |
| `forksCount` | Int! | | | `forksCount` | Int! | Number of times the project has been forked |
| `createdAt` | Time | | | `createdAt` | Time | Timestamp of the project creation |
| `lastActivityAt` | Time | | | `lastActivityAt` | Time | Timestamp of the project last activity |
| `archived` | Boolean | | | `archived` | Boolean | Archived status of the project |
| `visibility` | String | | | `visibility` | String | Visibility of the project |
| `containerRegistryEnabled` | Boolean | | | `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
| `sharedRunnersEnabled` | Boolean | | | `sharedRunnersEnabled` | Boolean | Indicates if shared runners are enabled on the project |
| `lfsEnabled` | Boolean | | | `lfsEnabled` | Boolean | Indicates if the project has Large File Storage (LFS) enabled |
| `mergeRequestsFfOnlyEnabled` | Boolean | | | `mergeRequestsFfOnlyEnabled` | Boolean | Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded. |
| `avatarUrl` | String | | | `avatarUrl` | String | URL to avatar image file of the project |
| `issuesEnabled` | Boolean | | | `issuesEnabled` | Boolean | (deprecated) Does this project have issues enabled?. Use `issues_access_level` instead |
| `mergeRequestsEnabled` | Boolean | | | `mergeRequestsEnabled` | Boolean | (deprecated) Does this project have merge_requests enabled?. Use `merge_requests_access_level` instead |
| `wikiEnabled` | Boolean | | | `wikiEnabled` | Boolean | (deprecated) Does this project have wiki enabled?. Use `wiki_access_level` instead |
| `snippetsEnabled` | Boolean | | | `snippetsEnabled` | Boolean | (deprecated) Does this project have snippets enabled?. Use `snippets_access_level` instead |
| `jobsEnabled` | Boolean | | | `jobsEnabled` | Boolean | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
| `publicJobs` | Boolean | | | `publicJobs` | Boolean | Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts |
| `openIssuesCount` | Int | | | `openIssuesCount` | Int | Number of open issues for the project |
| `importStatus` | String | | | `importStatus` | String | Status of project import background job of the project |
| `onlyAllowMergeIfPipelineSucceeds` | Boolean | | | `onlyAllowMergeIfPipelineSucceeds` | Boolean | Indicates if merge requests of the project can only be merged with successful jobs |
| `requestAccessEnabled` | Boolean | | | `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project |
| `onlyAllowMergeIfAllDiscussionsAreResolved` | Boolean | | | `onlyAllowMergeIfAllDiscussionsAreResolved` | Boolean | Indicates if merge requests of the project can only be merged when all the discussions are resolved |
| `printingMergeRequestLinkEnabled` | Boolean | | | `printingMergeRequestLinkEnabled` | Boolean | Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line |
| `removeSourceBranchAfterMerge` | Boolean | Remove the source branch by default after merge | | `removeSourceBranchAfterMerge` | Boolean | Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project |
| `namespace` | Namespace | | | `namespace` | Namespace | Namespace of the project |
| `group` | Group | | | `group` | Group | Group of the project |
| `statistics` | ProjectStatistics | | | `statistics` | ProjectStatistics | Statistics of the project |
| `repository` | Repository | | | `repository` | Repository | Git repository of the project |
| `mergeRequest` | MergeRequest | | | `mergeRequest` | MergeRequest | A single merge request of the project |
| `issue` | ExtendedIssue | | | `issue` | ExtendedIssue | A single issue of the project |
### ProjectPermissions ### ProjectPermissions
...@@ -670,13 +670,13 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -670,13 +670,13 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `commitCount` | Int! | | | `commitCount` | Int! | Commit count of the project |
| `storageSize` | Int! | | | `storageSize` | Int! | Storage size of the project |
| `repositorySize` | Int! | | | `repositorySize` | Int! | Repository size of the project |
| `lfsObjectsSize` | Int! | | | `lfsObjectsSize` | Int! | Large File Storage (LFS) object size of the project |
| `buildArtifactsSize` | Int! | | | `buildArtifactsSize` | Int! | Build artifacts size of the project |
| `packagesSize` | Int! | | | `packagesSize` | Int! | Packages size of the project |
| `wikiSize` | Int | | | `wikiSize` | Int | Wiki size of the project |
### RemoveAwardEmojiPayload ### RemoveAwardEmojiPayload
...@@ -690,10 +690,10 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -690,10 +690,10 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `rootRef` | String | | | `rootRef` | String | Default branch of the repository |
| `empty` | Boolean! | | | `empty` | Boolean! | Indicates repository has no visible content |
| `exists` | Boolean! | | | `exists` | Boolean! | Indicates a corresponding Git repository exists on disk |
| `tree` | Tree | | | `tree` | Tree | Tree of the repository |
### RootStorageStatistics ### RootStorageStatistics
...@@ -722,8 +722,8 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -722,8 +722,8 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `count` | Int! | | | `count` | Int! | Number of total tasks |
| `completedCount` | Int! | | | `completedCount` | Int! | Number of completed tasks |
### Todo ### Todo
...@@ -785,7 +785,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph ...@@ -785,7 +785,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `name` | String! | | | `name` | String! | Human-readable name of the user |
| `username` | String! | | | `username` | String! | Username of the user. Unique within this instance of GitLab |
| `avatarUrl` | String! | | | `avatarUrl` | String! | URL of the user's avatar |
| `webUrl` | String! | | | `webUrl` | String! | Web URL of the user |
...@@ -93,8 +93,8 @@ module Gitlab ...@@ -93,8 +93,8 @@ module Gitlab
docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now. docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
none: "", none: "",
qa: "~QA", qa: "~QA",
test: "~test for `spec/features/*`", test: "~test ~Quality for `spec/features/*`",
engineering_productivity: "Engineering Productivity for CI config review" engineering_productivity: '~"Engineering Productivity" for CI, Danger'
}.freeze }.freeze
CATEGORIES = { CATEGORIES = {
%r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`. %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`.
...@@ -104,7 +104,7 @@ module Gitlab ...@@ -104,7 +104,7 @@ module Gitlab
%r{\A(ee/)?public/} => :frontend, %r{\A(ee/)?public/} => :frontend,
%r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend, %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend, %r{\A(ee/)?vendor/assets/} => :frontend,
%r{\Ascripts/frontend/} => :frontend, %r{\A(ee/)?scripts/frontend/} => :frontend,
%r{(\A|/)( %r{(\A|/)(
\.babelrc | \.babelrc |
\.eslintignore | \.eslintignore |
...@@ -130,14 +130,18 @@ module Gitlab ...@@ -130,14 +130,18 @@ module Gitlab
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database, %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
%r{\Arubocop/cop/migration(/|\.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/)?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/features/} => :test,
%r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend, %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend,
%r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend, %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend,
%r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend, %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend,
%r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity, %r{\A(Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
%r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend, %r{\A[A-Z_]+_VERSION\z} => :backend,
%r{\A\.rubocop(_todo)?\.yml\z} => :backend, %r{\A\.rubocop(_todo)?\.yml\z} => :backend,
......
...@@ -67,7 +67,10 @@ module Gitlab ...@@ -67,7 +67,10 @@ module Gitlab
area && labels.any?("devops::#{area.downcase}") if kind == :reviewer area && labels.any?("devops::#{area.downcase}") if kind == :reviewer
when :engineering_productivity 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 else
capabilities(project).include?("#{kind} #{category}") capabilities(project).include?("#{kind} #{category}")
end end
......
...@@ -2898,6 +2898,9 @@ msgstr "" ...@@ -2898,6 +2898,9 @@ msgstr ""
msgid "Change assignee(s)." msgid "Change assignee(s)."
msgstr "" msgstr ""
msgid "Change branches"
msgstr ""
msgid "Change label" msgid "Change label"
msgstr "" msgstr ""
...@@ -7397,6 +7400,9 @@ msgstr "" ...@@ -7397,6 +7400,9 @@ msgstr ""
msgid "From %{providerTitle}" msgid "From %{providerTitle}"
msgstr "" msgstr ""
msgid "From %{source_title} into"
msgstr ""
msgid "From Bitbucket" msgid "From Bitbucket"
msgstr "" msgstr ""
...@@ -10287,6 +10293,9 @@ msgstr "" ...@@ -10287,6 +10293,9 @@ msgstr ""
msgid "Merge in progress" msgid "Merge in progress"
msgstr "" msgstr ""
msgid "Merge options"
msgstr ""
msgid "Merge request" msgid "Merge request"
msgstr "" msgstr ""
...@@ -14871,6 +14880,9 @@ msgstr "" ...@@ -14871,6 +14880,9 @@ msgstr ""
msgid "Select an existing Kubernetes cluster or create a new one" msgid "Select an existing Kubernetes cluster or create a new one"
msgstr "" msgstr ""
msgid "Select branch"
msgstr ""
msgid "Select branch/tag" msgid "Select branch/tag"
msgstr "" msgstr ""
......
...@@ -216,8 +216,7 @@ describe 'Dashboard Projects' do ...@@ -216,8 +216,7 @@ describe 'Dashboard Projects' do
expect(page).to have_selector('.merge-request-form') expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq project_new_merge_request_path(project) 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('#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(page).to have_content "From feature into master"
expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
end end
end end
......
...@@ -17,7 +17,7 @@ describe 'User edits a merge request', :js do ...@@ -17,7 +17,7 @@ describe 'User edits a merge request', :js do
end end
it 'changes the target branch' do 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') select2('merge-test', from: '#merge_request_target_branch')
click_button('Save changes') click_button('Save changes')
......
...@@ -55,12 +55,16 @@ describe 'Cherry-pick Commits' do ...@@ -55,12 +55,16 @@ describe 'Cherry-pick Commits' do
end end
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 it do
find('.header-action-buttons a.dropdown-toggle').click
find("a[href='#modal-cherry-pick-commit']").click find("a[href='#modal-cherry-pick-commit']").click
page.within('#modal-cherry-pick-commit') do page.within('#modal-cherry-pick-commit') do
click_button 'Cherry-pick' click_button 'Cherry-pick'
end 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("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") expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master")
end end
......
import Vue from 'vue'; import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import actionComponent from '~/pipelines/components/graph/action_component.vue'; import ActionComponent from '~/pipelines/components/graph/action_component.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('pipeline graph action component', () => { describe('pipeline graph action component', () => {
let component; let wrapper;
let mock; let mock;
beforeEach(done => { beforeEach(() => {
const ActionComponent = Vue.extend(actionComponent);
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mock.onPost('foo.json').reply(200); mock.onPost('foo.json').reply(200);
component = mountComponent(ActionComponent, { wrapper = mount(ActionComponent, {
tooltipText: 'bar', propsData: {
link: 'foo', tooltipText: 'bar',
actionIcon: 'cancel', link: 'foo',
actionIcon: 'cancel',
},
sync: false,
}); });
Vue.nextTick(done);
}); });
afterEach(() => { afterEach(() => {
mock.restore(); mock.restore();
component.$destroy(); wrapper.destroy();
}); });
it('should render the provided title as a bootstrap tooltip', () => { 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 => { it('should update bootstrap tooltip when title changes', done => {
component.tooltipText = 'changed'; wrapper.setProps({ tooltipText: 'changed' });
component wrapper.vm
.$nextTick() .$nextTick()
.then(() => { .then(() => {
expect(component.$el.getAttribute('data-original-title')).toBe('changed'); expect(wrapper.attributes('data-original-title')).toBe('changed');
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
it('should render an svg', () => { it('should render an svg', () => {
expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined(); expect(wrapper.find('.ci-action-icon-wrapper')).toBeDefined();
expect(component.$el.querySelector('svg')).toBeDefined(); expect(wrapper.find('svg')).toBeDefined();
}); });
describe('on click', () => { describe('on click', () => {
it('emits `pipelineActionRequestComplete` after a successful request', done => { it('emits `pipelineActionRequestComplete` after a successful request', done => {
spyOn(component, '$emit'); jest.spyOn(wrapper.vm, '$emit');
component.$el.click();
setTimeout(() => { wrapper.find('button').trigger('click');
component
.$nextTick()
.then(() => {
expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
})
.catch(done.fail);
done(); waitForPromises()
}, 0); .then(() => {
expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
done();
})
.catch(done.fail);
}); });
it('renders a loading icon while waiting for request', done => { it('renders a loading icon while waiting for request', done => {
component.$el.click(); wrapper.find('button').trigger('click');
component.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull(); expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
setTimeout(() => { done();
done();
});
}); });
}); });
}); });
......
...@@ -17,6 +17,7 @@ describe('Pipelines Triggerer', () => { ...@@ -17,6 +17,7 @@ describe('Pipelines Triggerer', () => {
const createComponent = () => { const createComponent = () => {
wrapper = mount(pipelineTriggerer, { wrapper = mount(pipelineTriggerer, {
propsData: mockData, propsData: mockData,
sync: false,
}); });
}; };
...@@ -49,6 +50,8 @@ describe('Pipelines Triggerer', () => { ...@@ -49,6 +50,8 @@ describe('Pipelines Triggerer', () => {
}, },
}); });
expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API'); wrapper.vm.$nextTick(() => {
expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
});
}); });
}); });
import Vue from 'vue'; import { mount } from '@vue/test-utils';
import tableRowComp from '~/pipelines/components/pipelines_table_row.vue'; import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub'; import eventHub from '~/pipelines/event_hub';
describe('Pipelines Table Row', () => { describe('Pipelines Table Row', () => {
const jsonFixtureName = 'pipelines/pipelines.json'; const jsonFixtureName = 'pipelines/pipelines.json';
const buildComponent = pipeline => {
const PipelinesTableRowComponent = Vue.extend(tableRowComp); const createWrapper = pipeline =>
return new PipelinesTableRowComponent({ mount(PipelinesTableRowComponent, {
el: document.querySelector('.test-dom-element'),
propsData: { propsData: {
pipeline, pipeline,
autoDevopsHelpPath: 'foo', autoDevopsHelpPath: 'foo',
viewType: 'root', viewType: 'root',
}, },
}).$mount(); sync: false,
}; });
let component; let wrapper;
let pipeline; let pipeline;
let pipelineWithoutAuthor; let pipelineWithoutAuthor;
let pipelineWithoutCommit; let pipelineWithoutCommit;
...@@ -32,28 +31,29 @@ describe('Pipelines Table Row', () => { ...@@ -32,28 +31,29 @@ describe('Pipelines Table Row', () => {
}); });
afterEach(() => { afterEach(() => {
component.$destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should render a table row', () => { 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', () => { describe('status column', () => {
beforeEach(() => { beforeEach(() => {
component = buildComponent(pipeline); wrapper = createWrapper(pipeline);
}); });
it('should render a pipeline link', () => { it('should render a pipeline link', () => {
expect( expect(wrapper.find('.table-section.commit-link a').attributes('href')).toEqual(
component.$el.querySelector('.table-section.commit-link a').getAttribute('href'), pipeline.path,
).toEqual(pipeline.path); );
}); });
it('should render status text', () => { 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, pipeline.details.status.text,
); );
}); });
...@@ -61,33 +61,32 @@ describe('Pipelines Table Row', () => { ...@@ -61,33 +61,32 @@ describe('Pipelines Table Row', () => {
describe('information column', () => { describe('information column', () => {
beforeEach(() => { beforeEach(() => {
component = buildComponent(pipeline); wrapper = createWrapper(pipeline);
}); });
it('should render a pipeline link', () => { it('should render a pipeline link', () => {
expect( expect(wrapper.find('.table-section:nth-child(2) a').attributes('href')).toEqual(
component.$el.querySelector('.table-section:nth-child(2) a').getAttribute('href'), pipeline.path,
).toEqual(pipeline.path); );
}); });
it('should render pipeline ID', () => { it('should render pipeline ID', () => {
expect( expect(wrapper.find('.table-section:nth-child(2) a > span').text()).toEqual(
component.$el.querySelector('.table-section:nth-child(2) a > span').textContent, `#${pipeline.id}`,
).toEqual(`#${pipeline.id}`); );
}); });
describe('when a user is provided', () => { describe('when a user is provided', () => {
it('should render user information', () => { it('should render user information', () => {
expect( expect(
component.$el wrapper.find('.table-section:nth-child(3) .js-pipeline-url-user').attributes('href'),
.querySelector('.table-section:nth-child(3) .js-pipeline-url-user')
.getAttribute('href'),
).toEqual(pipeline.user.path); ).toEqual(pipeline.user.path);
expect( expect(
component.$el wrapper
.querySelector('.table-section:nth-child(3) .js-user-avatar-image-toolip') .find('.table-section:nth-child(3) .js-user-avatar-image-toolip')
.textContent.trim(), .text()
.trim(),
).toEqual(pipeline.user.name); ).toEqual(pipeline.user.name);
}); });
}); });
...@@ -95,40 +94,47 @@ describe('Pipelines Table Row', () => { ...@@ -95,40 +94,47 @@ describe('Pipelines Table Row', () => {
describe('commit column', () => { describe('commit column', () => {
it('should render link to commit', () => { 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 findElements = () => {
const commitTitleElement = component.$el.querySelector('.branch-commit .commit-title'); const commitTitleElement = wrapper.find('.branch-commit .commit-title');
const commitAuthorElement = commitTitleElement.querySelector('a.avatar-image-container'); const commitAuthorElement = commitTitleElement.find('a.avatar-image-container');
if (!commitAuthorElement) { if (!commitAuthorElement.exists()) {
return { commitAuthorElement }; return {
commitAuthorElement,
};
} }
const commitAuthorLink = commitAuthorElement.getAttribute('href'); const commitAuthorLink = commitAuthorElement.attributes('href');
const commitAuthorName = commitAuthorElement const commitAuthorName = commitAuthorElement
.querySelector('.js-user-avatar-image-toolip') .find('.js-user-avatar-image-toolip')
.textContent.trim(); .text()
.trim();
return { commitAuthorElement, commitAuthorLink, commitAuthorName };
return {
commitAuthorElement,
commitAuthorLink,
commitAuthorName,
};
}; };
it('renders nothing without commit', () => { it('renders nothing without commit', () => {
expect(pipelineWithoutCommit.commit).toBe(null); expect(pipelineWithoutCommit.commit).toBe(null);
component = buildComponent(pipelineWithoutCommit);
wrapper = createWrapper(pipelineWithoutCommit);
const { commitAuthorElement } = findElements(); const { commitAuthorElement } = findElements();
expect(commitAuthorElement).toBe(null); expect(commitAuthorElement.exists()).toBe(false);
}); });
it('renders commit author', () => { it('renders commit author', () => {
component = buildComponent(pipeline); wrapper = createWrapper(pipeline);
const { commitAuthorLink, commitAuthorName } = findElements(); const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(pipeline.commit.author.path); expect(commitAuthorLink).toEqual(pipeline.commit.author.path);
...@@ -137,8 +143,8 @@ describe('Pipelines Table Row', () => { ...@@ -137,8 +143,8 @@ describe('Pipelines Table Row', () => {
it('renders commit with unregistered author', () => { it('renders commit with unregistered author', () => {
expect(pipelineWithoutAuthor.commit.author).toBe(null); expect(pipelineWithoutAuthor.commit.author).toBe(null);
component = buildComponent(pipelineWithoutAuthor);
wrapper = createWrapper(pipelineWithoutAuthor);
const { commitAuthorLink, commitAuthorName } = findElements(); const { commitAuthorLink, commitAuthorName } = findElements();
expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`); expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`);
...@@ -148,13 +154,12 @@ describe('Pipelines Table Row', () => { ...@@ -148,13 +154,12 @@ describe('Pipelines Table Row', () => {
describe('stages column', () => { describe('stages column', () => {
beforeEach(() => { beforeEach(() => {
component = buildComponent(pipeline); wrapper = createWrapper(pipeline);
}); });
it('should render an icon for each stage', () => { it('should render an icon for each stage', () => {
expect( expect(
component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button') wrapper.findAll('.table-section:nth-child(4) .js-builds-dropdown-button').length,
.length,
).toEqual(pipeline.details.stages.length); ).toEqual(pipeline.details.stages.length);
}); });
}); });
...@@ -172,44 +177,49 @@ describe('Pipelines Table Row', () => { ...@@ -172,44 +177,49 @@ describe('Pipelines Table Row', () => {
withActions.cancel_path = '/cancel'; withActions.cancel_path = '/cancel';
withActions.retry_path = '/retry'; withActions.retry_path = '/retry';
component = buildComponent(withActions); wrapper = createWrapper(withActions);
}); });
it('should render the provided actions', () => { it('should render the provided actions', () => {
expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull(); expect(wrapper.find('.js-pipelines-retry-button').exists()).toBe(true);
expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull(); expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true);
const dropdownMenu = component.$el.querySelectorAll('.dropdown-menu'); 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', () => { it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
eventHub.$on('retryPipeline', endpoint => { eventHub.$on('retryPipeline', endpoint => {
expect(endpoint).toEqual('/retry'); expect(endpoint).toBe('/retry');
}); });
component.$el.querySelector('.js-pipelines-retry-button').click(); wrapper.find('.js-pipelines-retry-button').trigger('click');
expect(wrapper.vm.isRetrying).toBe(true);
expect(component.isRetrying).toEqual(true);
}); });
it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => { it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => {
eventHub.$once('openConfirmationModal', data => { eventHub.$once('openConfirmationModal', data => {
const { id, ref, commit } = pipeline; const { id, ref, commit } = pipeline;
expect(data.endpoint).toEqual('/cancel'); expect(data.endpoint).toBe('/cancel');
expect(data.pipeline).toEqual(jasmine.objectContaining({ id, ref, commit })); 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 => { it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => {
component.cancelingPipeline = pipeline.id; wrapper.setProps({ cancelingPipeline: pipeline.id });
component wrapper.vm
.$nextTick() .$nextTick()
.then(() => { .then(() => {
expect(component.isCancelling).toEqual(true); expect(wrapper.vm.isCancelling).toBe(true);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
...@@ -178,6 +178,7 @@ describe Gitlab::Danger::Helper do ...@@ -178,6 +178,7 @@ describe Gitlab::Danger::Helper do
'app/assets/foo' | :frontend 'app/assets/foo' | :frontend
'app/views/foo' | :frontend 'app/views/foo' | :frontend
'public/foo' | :frontend 'public/foo' | :frontend
'scripts/frontend/foo' | :frontend
'spec/javascripts/foo' | :frontend 'spec/javascripts/foo' | :frontend
'spec/frontend/bar' | :frontend 'spec/frontend/bar' | :frontend
'vendor/assets/foo' | :frontend 'vendor/assets/foo' | :frontend
...@@ -193,10 +194,8 @@ describe Gitlab::Danger::Helper do ...@@ -193,10 +194,8 @@ describe Gitlab::Danger::Helper do
'app/models/foo' | :backend 'app/models/foo' | :backend
'bin/foo' | :backend 'bin/foo' | :backend
'config/foo' | :backend 'config/foo' | :backend
'danger/foo' | :backend
'lib/foo' | :backend 'lib/foo' | :backend
'rubocop/foo' | :backend 'rubocop/foo' | :backend
'scripts/foo' | :backend
'spec/foo' | :backend 'spec/foo' | :backend
'spec/foo/bar' | :backend 'spec/foo/bar' | :backend
...@@ -209,16 +208,24 @@ describe Gitlab::Danger::Helper do ...@@ -209,16 +208,24 @@ describe Gitlab::Danger::Helper do
'vendor/languages.yml' | :backend 'vendor/languages.yml' | :backend
'vendor/licenses.csv' | :backend 'vendor/licenses.csv' | :backend
'Dangerfile' | :backend
'Gemfile' | :backend 'Gemfile' | :backend
'Gemfile.lock' | :backend 'Gemfile.lock' | :backend
'Procfile' | :backend 'Procfile' | :backend
'Rakefile' | :backend 'Rakefile' | :backend
'FOO_VERSION' | :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.yml' | :engineering_productivity
'.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity
'.gitlab/ci/ee-specific-checks.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 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend
'ee/FOO_VERSION' | :unknown 'ee/FOO_VERSION' | :unknown
......
...@@ -30,7 +30,7 @@ describe Gitlab::Danger::Teammate do ...@@ -30,7 +30,7 @@ describe Gitlab::Danger::Teammate do
expect(subject.maintainer?(project, :frontend, labels)).to be_truthy expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
end 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'] } let(:labels) { ['devops::create'] }
context 'when role is Test Automation Engineer, Create' do context 'when role is Test Automation Engineer, Create' do
...@@ -79,6 +79,22 @@ describe Gitlab::Danger::Teammate do ...@@ -79,6 +79,22 @@ describe Gitlab::Danger::Teammate do
it '#maintainer? returns false' do it '#maintainer? returns false' do
expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey
end 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 end
end end
......
...@@ -262,7 +262,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do ...@@ -262,7 +262,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'has the correct number of pipelines and statuses' do it 'has the correct number of pipelines and statuses' do
expect(@project.ci_pipelines.size).to eq(6) 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)| .each do |(pipeline, expected_status_size)|
expect(pipeline.statuses.size).to eq(expected_status_size) expect(pipeline.statuses.size).to eq(expected_status_size)
end 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