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
This diff is collapsed.
...@@ -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
This diff is collapsed.
...@@ -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
This diff is collapsed.
...@@ -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