diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-31 12:06:26 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-10-31 12:06:26 +0000 |
commit | 0be510a49f6e4f8e27b19b707fd1dac61571f78f (patch) | |
tree | 97ca0053d4fad66e900d25fdba61b2adb611efb0 | |
parent | 6026bddcd51eca573c530240c421392045172b89 (diff) | |
download | gitlab-ce-0be510a49f6e4f8e27b19b707fd1dac61571f78f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
41 files changed, 877 insertions, 632 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 61653d798a9..9ad0e604d61 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -8,6 +8,7 @@ # Frontend maintainers should see everything in `app/assets/` app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill *.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill +/scripts/frontend/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina @iamphill # Database maintainers should review changes in `db/` db/ @gitlab-org/maintainers/database diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index dfc39d8e03b..a7d94281008 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -21,6 +21,27 @@ $spacing-scale: ( ); /* + * 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 */ $darken-normal-factor: 7%; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b2c1d0b6dc5..3f8bdc82eff 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -252,6 +252,7 @@ .fa-caret-down { margin-left: 3px; + line-height: 0; &.dropdown-btn-icon { margin-left: 0; @@ -269,7 +270,7 @@ } .count-badge, - .btn-xs { + .btn { height: 24px; } diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index d2906ce0780..4a0b6ac1ddd 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -16,6 +16,12 @@ } } +@each $index, $size in $size-scale { + #{'.mw-#{$index}'} { + max-width: $size; + } +} + .border-width-1px { border-width: 1px; } .border-style-dashed { border-style: dashed; } .border-style-solid { border-style: solid; } diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index c97fec0a6ee..e5d4a4bb073 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -16,12 +16,7 @@ class NotificationSettingsController < ApplicationController @notification_setting = current_user.notification_settings.find(params[:id]) @saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source)) - if params[:hide_label].present? - btn_class = params[:project_id].present? ? 'btn-xs' : '' - render_response("shared/notifications/_new_button", btn_class) - else - render_response - end + render_response end private @@ -42,7 +37,16 @@ class NotificationSettingsController < ApplicationController can?(current_user, ability_name, resource) end - def render_response(response_template = "shared/notifications/_button", btn_class = nil) + def render_response + btn_class = nil + + if params[:hide_label].present? + btn_class = 'btn-xs' if params[:project_id].present? + response_template = 'shared/notifications/_new_button' + else + response_template = 'shared/notifications/_button' + end + render json: { html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class), saved: @saved diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb index fe71791f413..d5600881728 100644 --- a/app/graphql/types/commit_type.rb +++ b/app/graphql/types/commit_type.rb @@ -8,23 +8,31 @@ module Types present_using CommitPresenter - field :id, type: GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :sha, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :title, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :description, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :message, type: GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :authored_date, type: Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - field :web_url, type: GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :signature_html, type: GraphQL::STRING_TYPE, - null: true, calls_gitaly: true, description: 'Rendered html for the commit signature' + field :id, type: GraphQL::ID_TYPE, null: false, + description: 'ID (global ID) of the commit' + field :sha, type: GraphQL::STRING_TYPE, null: false, + description: 'SHA1 ID of the commit' + field :title, type: GraphQL::STRING_TYPE, null: true, + description: 'Title of the commit message' + field :description, type: GraphQL::STRING_TYPE, null: true, + description: 'Description of the commit message' + field :message, type: GraphQL::STRING_TYPE, null: true, + description: 'Raw commit message' + field :authored_date, type: Types::TimeType, null: true, + description: 'Timestamp of when the commit was authored' + field :web_url, type: GraphQL::STRING_TYPE, null: false, + description: 'Web URL of the commit' + field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true, + description: 'Rendered HTML of the commit signature' # models/commit lazy loads the author by email - field :author, type: Types::UserType, null: true # rubocop:disable Graphql/Descriptions + field :author, type: Types::UserType, null: true, + description: 'Author of the commit' field :latest_pipeline, type: Types::Ci::PipelineType, null: true, - description: "Latest pipeline for this commit", + description: "Latest pipeline of the commit", resolve: -> (obj, ctx, args) do Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 1e52c0cb147..386ae6ed4a3 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -8,14 +8,17 @@ module Types expose_permissions Types::PermissionTypes::Group - field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :web_url, GraphQL::STRING_TYPE, null: false, + description: 'Web URL of the group' - field :avatar_url, GraphQL::STRING_TYPE, null: true, resolve: -> (group, args, ctx) do # rubocop:disable Graphql/Descriptions - group.avatar_url(only_path: false) - end + field :avatar_url, GraphQL::STRING_TYPE, null: true, + description: 'Avatar URL of the group', + resolve: -> (group, args, ctx) do + group.avatar_url(only_path: false) + end - field :parent, GroupType, # rubocop:disable Graphql/Descriptions - null: true, + field :parent, GroupType, null: true, + description: 'Parent group', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find } end end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 4965601fe65..bf055bd9eef 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -12,53 +12,77 @@ module Types present_using IssuePresenter - field :iid, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :iid, GraphQL::ID_TYPE, null: false, + description: "Internal ID of the issue" + field :title, GraphQL::STRING_TYPE, null: false, + description: 'Title of the issue' markdown_field :title_html, null: true - field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Description of the issue' markdown_field :description_html, null: true - field :state, IssueStateEnum, null: false # rubocop:disable Graphql/Descriptions - - field :reference, GraphQL::STRING_TYPE, null: false, method: :to_reference do # rubocop:disable Graphql/Descriptions - argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false # rubocop:disable Graphql/Descriptions + field :state, IssueStateEnum, null: false, + description: 'State of the issue' + + field :reference, GraphQL::STRING_TYPE, null: false, + description: 'Internal reference of the issue. Returned in shortened format by default', + method: :to_reference do + argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false, + description: 'Boolean option specifying whether the reference should be returned in full' end - field :author, Types::UserType, # rubocop:disable Graphql/Descriptions - null: false, + field :author, Types::UserType, null: false, + description: 'User that created the issue', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } # Remove complexity when BatchLoader is used - field :assignees, Types::UserType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions + field :assignees, Types::UserType.connection_type, null: true, complexity: 5, + description: 'Assignees of the issue' # Remove complexity when BatchLoader is used - field :labels, Types::LabelType.connection_type, null: true, complexity: 5 # rubocop:disable Graphql/Descriptions - field :milestone, Types::MilestoneType, # rubocop:disable Graphql/Descriptions - null: true, + field :labels, Types::LabelType.connection_type, null: true, complexity: 5, + description: 'Labels of the issue' + field :milestone, Types::MilestoneType, null: true, + description: 'Milestone of the issue', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } - field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - field :confidential, GraphQL::BOOLEAN_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :discussion_locked, GraphQL::BOOLEAN_TYPE, # rubocop:disable Graphql/Descriptions - null: false, + field :due_date, Types::TimeType, null: true, + description: 'Due date of the issue' + field :confidential, GraphQL::BOOLEAN_TYPE, null: false, + description: 'Indicates the issue is confidential' + field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false, + description: 'Indicates discussion is locked on the issue', resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked } - field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :user_notes_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path # rubocop:disable Graphql/Descriptions - field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :relative_position, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'List of participants for the issue' - field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate on the issue' - field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on the issue' - - field :closed_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - - field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions - field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions - - field :task_completion_status, Types::TaskCompletionStatus, null: false # rubocop:disable Graphql/Descriptions + field :upvotes, GraphQL::INT_TYPE, null: false, + description: 'Number of upvotes the issue has received' + field :downvotes, GraphQL::INT_TYPE, null: false, + description: 'Number of downvotes the issue has received' + field :user_notes_count, GraphQL::INT_TYPE, null: false, + description: 'Number of user notes of the issue' + field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path, + description: 'Web path of the issue' + field :web_url, GraphQL::STRING_TYPE, null: false, + description: 'Web URL of the issue' + field :relative_position, GraphQL::INT_TYPE, null: true, + description: 'Relative position of the issue (used for positioning in epic tree and issue boards)' + + field :participants, Types::UserType.connection_type, null: true, complexity: 5, + description: 'List of participants in the issue' + field :time_estimate, GraphQL::INT_TYPE, null: false, + description: 'Time estimate of the issue' + field :total_time_spent, GraphQL::INT_TYPE, null: false, + description: 'Total time reported as spent on the issue' + + field :closed_at, Types::TimeType, null: true, + description: 'Timestamp of when the issue was closed' + + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the issue was created' + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the issue was last updated' + + field :task_completion_status, Types::TaskCompletionStatus, null: false, + description: 'Task completion status of the issue' end end diff --git a/app/graphql/types/label_type.rb b/app/graphql/types/label_type.rb index 384a27df563..b21503540f8 100644 --- a/app/graphql/types/label_type.rb +++ b/app/graphql/types/label_type.rb @@ -6,10 +6,14 @@ module Types authorize :read_label - field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Description of the label (markdown rendered as HTML for caching)' markdown_field :description_html, null: true - field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :text_color, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :title, GraphQL::STRING_TYPE, null: false, + description: 'Content of the label' + field :color, GraphQL::STRING_TYPE, null: false, + description: 'Background color of the label' + field :text_color, GraphQL::STRING_TYPE, null: false, + description: 'Text color of the label' end end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 71a65dc6713..278a95fe3ca 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -12,70 +12,116 @@ module Types present_using MergeRequestPresenter - field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :iid, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the merge request' + 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 - 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 - field :state, MergeRequestStateEnum, null: false # rubocop:disable Graphql/Descriptions - field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions - field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions - field :source_project, Types::ProjectType, null: true # rubocop:disable Graphql/Descriptions - field :target_project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions - field :diff_refs, Types::DiffRefsType, null: true # rubocop:disable Graphql/Descriptions - # Alias for target_project - field :project, Types::ProjectType, null: false # rubocop:disable Graphql/Descriptions - field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id # rubocop:disable Graphql/Descriptions - field :source_project_id, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :target_project_id, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :source_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :target_branch, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false # rubocop:disable Graphql/Descriptions - field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :diff_head_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :user_notes_count, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions - field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true # rubocop:disable Graphql/Descriptions - field :merge_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :in_progress_merge_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :merge_error, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false # rubocop:disable Graphql/Descriptions - field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true # rubocop:disable Graphql/Descriptions - # rubocop:disable Graphql/Descriptions - field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true, deprecation_reason: "Renamed to defaultMergeCommitMessage" - # rubocop:enable Graphql/Descriptions - field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false # rubocop:disable Graphql/Descriptions - field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false # rubocop:disable Graphql/Descriptions - field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :upvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :downvotes, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :state, MergeRequestStateEnum, null: false, + description: 'State of the merge request' + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the merge request was created' + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the merge request was last updated' + field :source_project, Types::ProjectType, null: true, + description: 'Source project of the merge request' + field :target_project, Types::ProjectType, null: false, + description: 'Target project of the merge request' + field :diff_refs, Types::DiffRefsType, null: true, + description: 'References of the base SHA, the head SHA, and the start SHA for this merge request' + field :project, Types::ProjectType, null: false, + description: 'Alias for target_project' + field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id, + description: 'ID of the merge request project' + field :source_project_id, GraphQL::INT_TYPE, null: true, + description: 'ID of the merge request source project' + field :target_project_id, GraphQL::INT_TYPE, null: false, + description: 'ID of the merge request target project' + field :source_branch, GraphQL::STRING_TYPE, null: false, + description: 'Source branch of the merge request' + field :target_branch, GraphQL::STRING_TYPE, null: false, + description: 'Target branch of the merge request' + field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false, + description: 'Indicates if the merge request is a work in progress (WIP)' + field :merge_when_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)' + field :diff_head_sha, GraphQL::STRING_TYPE, null: true, + description: 'Diff head SHA of the merge request' + field :merge_commit_sha, GraphQL::STRING_TYPE, null: true, + description: 'SHA of the merge request commit (set once merged)' + field :user_notes_count, GraphQL::INT_TYPE, null: true, + description: 'User notes count of the merge request' + field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true, + description: 'Indicates if the source branch of the merge request will be deleted after merge' + 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 :pipelines, Types::Ci::PipelineType.connection_type, # rubocop:disable Graphql/Descriptions + field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, + 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 - field :milestone, Types::MilestoneType, description: 'The milestone this merge request is linked to', - null: true, + field :milestone, Types::MilestoneType, null: true, + description: 'The milestone of the merge request', 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 :participants, Types::UserType.connection_type, null: true, complexity: 5, description: 'The list of participants on the merge request' + field :assignees, Types::UserType.connection_type, null: true, complexity: 5, + 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, - description: 'Boolean flag for whether the currently logged in user is subscribed to this MR' - field :labels, Types::LabelType.connection_type, null: true, complexity: 5, description: 'The list of labels on the merge request' - field :discussion_locked, GraphQL::BOOLEAN_TYPE, description: 'Boolean flag determining if comments on the merge request are locked to members only', + 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: '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, resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked } - field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'The time estimate for the merge request' - field :total_time_spent, GraphQL::INT_TYPE, null: false, description: 'Total time reported as spent on 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 - argument :full, GraphQL::BOOLEAN_TYPE, required: false, default_value: false, description: 'Boolean option specifying whether the reference should be returned in full' + field :time_estimate, GraphQL::INT_TYPE, null: false, + description: 'Time estimate of the merge request' + field :total_time_spent, GraphQL::INT_TYPE, null: false, + 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 - 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 diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb index bfcb929f5ac..1998b036a53 100644 --- a/app/graphql/types/metadata_type.rb +++ b/app/graphql/types/metadata_type.rb @@ -6,7 +6,9 @@ module Types authorize :read_instance_metadata - field :version, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :revision, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :version, GraphQL::STRING_TYPE, null: false, + description: 'Version' + field :revision, GraphQL::STRING_TYPE, null: false, + description: 'Revision' end end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index 78d0a8220ec..1d3a1231bca 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -6,14 +6,21 @@ module Types authorize :read_milestone - field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :title, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :state, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Description of the milestone' + field :title, GraphQL::STRING_TYPE, null: false, + description: 'Title of the milestone' + field :state, GraphQL::STRING_TYPE, null: false, + description: 'State of the milestone' - field :due_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - field :start_date, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions + field :due_date, Types::TimeType, null: true, + description: 'Timestamp of the milestone due date' + field :start_date, Types::TimeType, null: true, + description: 'Timestamp of the milestone start date' - field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions - field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of milestone creation' + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of last milestone update' end end diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index cc1d06b19e1..1714284a5cf 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -6,27 +6,35 @@ module Types authorize :read_namespace - field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the namespace' - field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :full_name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :full_path, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :name, GraphQL::STRING_TYPE, null: false, + description: 'Name of the namespace' + field :path, GraphQL::STRING_TYPE, null: false, + description: 'Path of the namespace' + field :full_name, GraphQL::STRING_TYPE, null: false, + description: 'Full name of the namespace' + field :full_path, GraphQL::ID_TYPE, null: false, + description: 'Full path of the namespace' - field :description, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Description of the namespace' markdown_field :description_html, null: true - field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled? # rubocop:disable Graphql/Descriptions - field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions + field :visibility, GraphQL::STRING_TYPE, null: true, + description: 'Visibility of the namespace' + field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?, + description: 'Indicates if Large File Storage (LFS) is enabled for namespace' + field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates if users can request access to namespace' field :root_storage_statistics, Types::RootStorageStatisticsType, null: true, - description: 'The aggregated storage statistics. Only available for root namespaces', + description: 'Aggregated storage statistics of the namespace. Only available for root namespaces', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find } - field :projects, # rubocop:disable Graphql/Descriptions - Types::ProjectType.connection_type, - null: false, + field :projects, Types::ProjectType.connection_type, null: false, + description: 'Projects within this namespace', resolver: ::Resolvers::NamespaceProjectsResolver end end diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb index 5045471a75b..c46410df6c0 100644 --- a/app/graphql/types/project_statistics_type.rb +++ b/app/graphql/types/project_statistics_type.rb @@ -6,13 +6,20 @@ module Types authorize :read_statistics - field :commit_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :commit_count, GraphQL::INT_TYPE, null: false, + description: 'Commit count of the project' - field :storage_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :repository_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :lfs_objects_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :build_artifacts_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :packages_size, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :wiki_size, GraphQL::INT_TYPE, null: true # rubocop:disable Graphql/Descriptions + field :storage_size, GraphQL::INT_TYPE, null: false, + description: 'Storage size of the project' + field :repository_size, GraphQL::INT_TYPE, null: false, + description: 'Repository size of the project' + field :lfs_objects_size, GraphQL::INT_TYPE, null: false, + description: 'Large File Storage (LFS) object size of the project' + field :build_artifacts_size, GraphQL::INT_TYPE, null: false, + description: 'Build artifacts size of the project' + field :packages_size, GraphQL::INT_TYPE, null: false, + description: 'Packages size of the project' + field :wiki_size, GraphQL::INT_TYPE, null: true, + description: 'Wiki size of the project' end end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 3f05610cf1f..f1e735508cc 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -8,98 +8,142 @@ module Types 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 :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :full_path, GraphQL::ID_TYPE, null: false, + 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, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :name_with_namespace, GraphQL::STRING_TYPE, null: false, + 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 - field :tag_list, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - - 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 :web_url, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :star_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true # 4 times # rubocop:disable Graphql/Descriptions - - field :created_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - field :last_activity_at, Types::TimeType, null: true # rubocop:disable Graphql/Descriptions - - field :archived, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :visibility, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions - project.avatar_url(only_path: false) - end + field :tag_list, GraphQL::STRING_TYPE, null: true, + description: 'List of project tags' + + field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true, + description: 'URL to connect to the project via SSH' + field :http_url_to_repo, GraphQL::STRING_TYPE, null: true, + description: 'URL to connect to the project via HTTPS' + field :web_url, GraphQL::STRING_TYPE, null: true, + description: 'Web URL of the project' + + field :star_count, GraphQL::INT_TYPE, null: false, + description: 'Number of times the project has been starred' + field :forks_count, GraphQL::INT_TYPE, null: false, calls_gitaly: true, # 4 times + description: 'Number of times the project has been forked' + + field :created_at, Types::TimeType, null: true, + description: 'Timestamp of the project creation' + field :last_activity_at, Types::TimeType, null: true, + description: 'Timestamp of the project last activity' + + field :archived, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Archived status of the project' + + 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| - field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions - project.feature_available?(feature, ctx[:current_user]) - end + field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, + description: "(deprecated) Does this project have #{feature} enabled?. Use `#{feature}_access_level` instead", + resolve: -> (project, args, ctx) do + project.feature_available?(feature, ctx[:current_user]) + end end - field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions - project.feature_available?(:builds, ctx[:current_user]) - end - - field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true # rubocop:disable Graphql/Descriptions - - field :open_issues_count, GraphQL::INT_TYPE, null: true, resolve: -> (project, args, ctx) do # rubocop:disable Graphql/Descriptions - project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) - end - - field :import_status, GraphQL::STRING_TYPE, null: true # rubocop:disable Graphql/Descriptions - - field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true # rubocop:disable Graphql/Descriptions - 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 :remove_source_branch_after_merge, GraphQL::BOOLEAN_TYPE, null: true, description: 'Remove the source branch by default after merge' - - field :namespace, Types::NamespaceType, null: true # rubocop:disable Graphql/Descriptions - field :group, Types::GroupType, null: true # rubocop:disable Graphql/Descriptions - - field :statistics, Types::ProjectStatisticsType, # rubocop:disable Graphql/Descriptions + field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: '(deprecated) Enable jobs for this project. Use `builds_access_level` instead', + resolve: -> (project, args, ctx) do + project.feature_available?(:builds, ctx[:current_user]) + end + + field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true, + description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts' + + field :open_issues_count, GraphQL::INT_TYPE, null: true, + description: 'Number of open issues for the project', + resolve: -> (project, args, ctx) do + project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) + end + + field :import_status, GraphQL::STRING_TYPE, null: true, + description: 'Status of project import background job of the project' + + field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates if merge requests of the project can only be merged with successful jobs' + field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true, + 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, + description: 'Statistics of the project', 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, null: true, + description: 'Merge requests of the project', resolver: Resolvers::MergeRequestsResolver - field :merge_request, # rubocop:disable Graphql/Descriptions + field :merge_request, Types::MergeRequestType, null: true, + description: 'A single merge request of the project', resolver: Resolvers::MergeRequestsResolver.single - field :issues, # rubocop:disable Graphql/Descriptions + field :issues, Types::IssueType.connection_type, null: true, + description: 'Issues of the project', resolver: Resolvers::IssuesResolver - field :issue, # rubocop:disable Graphql/Descriptions + field :issue, Types::ExtendedIssueType, null: true, + description: 'A single issue of the project', resolver: Resolvers::IssuesResolver.single - field :pipelines, # rubocop:disable Graphql/Descriptions + field :pipelines, Types::Ci::PipelineType.connection_type, null: true, + description: 'Build pipelines of the project', resolver: Resolvers::ProjectPipelinesResolver end end diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb index 9ecd336b41d..f0c25e13a26 100644 --- a/app/graphql/types/repository_type.rb +++ b/app/graphql/types/repository_type.rb @@ -6,9 +6,13 @@ module Types authorize :download_code - field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true # rubocop:disable Graphql/Descriptions - field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true # rubocop:disable Graphql/Descriptions - field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists? # rubocop:disable Graphql/Descriptions - field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true # rubocop:disable Graphql/Descriptions + field :root_ref, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, + description: 'Default branch of the repository' + field :empty, GraphQL::BOOLEAN_TYPE, null: false, method: :empty?, calls_gitaly: true, + description: 'Indicates repository has no visible content' + field :exists, GraphQL::BOOLEAN_TYPE, null: false, method: :exists?, + description: 'Indicates a corresponding Git repository exists on disk' + field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true, + description: 'Tree of the repository' end end diff --git a/app/graphql/types/task_completion_status.rb b/app/graphql/types/task_completion_status.rb index 0aa8fc60a7c..73a8b4f3020 100644 --- a/app/graphql/types/task_completion_status.rb +++ b/app/graphql/types/task_completion_status.rb @@ -8,8 +8,10 @@ module Types graphql_name 'TaskCompletionStatus' description 'Completion status of tasks' - field :count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :completed_count, GraphQL::INT_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :count, GraphQL::INT_TYPE, null: false, + description: 'Number of total tasks' + field :completed_count, GraphQL::INT_TYPE, null: false, + description: 'Number of completed tasks' end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 1ba37927b40..b45c7893e75 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -8,12 +8,16 @@ module Types present_using UserPresenter - field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :username, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :avatar_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions - field :web_url, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions + field :name, GraphQL::STRING_TYPE, null: false, + description: 'Human-readable name of the user' + field :username, GraphQL::STRING_TYPE, null: false, + description: 'Username of the user. Unique within this instance of GitLab' + field :avatar_url, GraphQL::STRING_TYPE, null: false, + description: "URL of the user's avatar" + field :web_url, GraphQL::STRING_TYPE, null: false, + description: 'Web URL of the user' field :todos, Types::TodoType.connection_type, null: false, resolver: Resolvers::TodoResolver, - description: 'Todos of this user' + description: 'Todos of the user' end end diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index 543441b9479..15c83f92474 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -1,15 +1,5 @@ %h3.page-title New Merge Request -%p.slead - - source_title, target_title = format_mr_branch_names(@merge_request) - From - %strong.ref-name= source_title - %span into - %strong.ref-name= target_title - - %span.float-right - = link_to 'Change branches', mr_change_branches_path(@merge_request) -%hr = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits, presenter: @mr_presenter = f.hidden_field :source_project_id diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 03159f123f3..318c9d809c1 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -2,5 +2,4 @@ %h3.page-title Edit Merge Request #{@merge_request.to_reference} -%hr = render 'form' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 5e2b5f95ee3..a8aae03aad7 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -12,6 +12,9 @@ = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs += render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form + +%hr .form-group.row = form.label :title, class: 'col-form-label col-sm-2' @@ -34,8 +37,6 @@ = render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form -= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form - = render 'shared/issuable/form/merge_params', issuable: issuable = render 'shared/issuable/form/contribution', issuable: issuable, form: form diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index fbc96baa0f7..03eebe7c987 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -4,21 +4,19 @@ - return unless issuable.is_a?(MergeRequest) - return if issuable.closed_without_fork? -%hr -- if issuable.new_record? - .form-group.row - = form.label :source_branch, class: 'col-form-label col-sm-2' - .col-sm-10 - .issuable-form-select-holder - = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 ref-name', disabled: true }) -.form-group.row - = form.label :target_branch, class: 'col-form-label col-sm-2' - .col-sm-10.target-branch-select-dropdown-container - .issuable-form-select-holder - = form.hidden_field(:target_branch, - { class: 'target_branch js-target-branch-select ref-name', - disabled: issuable.new_record?, - data: { placeholder: "Select branch", endpoint: refs_project_path(@project, sort: 'updated_desc', find: 'branches') }}) +- source_title, target_title = format_mr_branch_names(@merge_request) + +.form-group.row.d-flex.gl-pl-3.gl-pr-3.branch-selector + .align-self-center + %span= s_('From %{source_title} into').html_safe % { source_title: "<code>#{source_title}</code>".html_safe } - if issuable.new_record? + %code= target_title - = 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') }}) diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index f0c4acdd07f..1b557214e02 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -3,17 +3,17 @@ - return unless issuable.is_a?(MergeRequest) - return if issuable.closed_without_fork? -- if issuable.can_remove_source_branch?(current_user) - .form-group.row - .col-sm-10.offset-sm-2 - .form-check +.form-group.row + .col-sm-2.col-form-label.pt-sm-0 + %label + = _('Merge options') + .col-sm-10 + - if issuable.can_remove_source_branch?(current_user) + .form-check.append-bottom-default = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input' = label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do Delete source branch when merge request is accepted. - -.form-group.row - .col-sm-10.offset-sm-2 .form-check = hidden_field_tag 'merge_request[squash]', '0', id: nil = check_box_tag 'merge_request[squash]', '1', issuable.squash, class: 'form-check-input' diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 441abd57334..2b3e986a841 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -17,14 +17,14 @@ .js-notification-toggle-btns %div{ class: ("btn-group" if notification_setting.custom?) } - if notification_setting.custom? - %button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) - %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } + %button.btn.dropdown-toggle.d-flex{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon('caret-down') .sr-only Toggle dropdown - else - %button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } + %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } .float-left = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index 43a87fd8397..1fef43c0c37 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -1,3 +1,5 @@ +- hide_label = local_assigns.fetch(:hide_label, false) + .modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" } .modal-dialog .modal-content @@ -11,6 +13,7 @@ .container-fluid = form_for notification_setting, html: { class: "custom-notifications-form" } do |f| = hidden_setting_source_input(notification_setting) + = hidden_field_tag("hide_label", true) if hide_label .row .col-lg-4 %h4.prepend-top-0= _('Notification events') diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml index 3c8cc023848..363053b5e35 100644 --- a/app/views/shared/notifications/_new_button.html.haml +++ b/app/views/shared/notifications/_new_button.html.haml @@ -31,4 +31,4 @@ = render "shared/notifications/notification_dropdown", notification_setting: notification_setting = content_for :scripts_body do - = render "shared/notifications/custom_notifications", notification_setting: notification_setting + = render "shared/notifications/custom_notifications", notification_setting: notification_setting, hide_label: true diff --git a/changelogs/unreleased/maintenance-move-branch-selector-to-top.yml b/changelogs/unreleased/maintenance-move-branch-selector-to-top.yml new file mode 100644 index 00000000000..78455c49116 --- /dev/null +++ b/changelogs/unreleased/maintenance-move-branch-selector-to-top.yml @@ -0,0 +1,6 @@ +--- +title: Reduce new MR page redundancy by moving the source/target branch selector to + the top +merge_request: 17559 +author: +type: changed diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3f05b80ab63..a8c4e966099 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -47,16 +47,16 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `id` | ID! | | -| `sha` | String! | | -| `title` | String | | -| `description` | String | | -| `message` | String | | -| `authoredDate` | Time | | -| `webUrl` | String! | | -| `signatureHtml` | String | Rendered html for the commit signature | -| `author` | User | | -| `latestPipeline` | Pipeline | Latest pipeline for this commit | +| `id` | ID! | ID (global ID) of the commit | +| `sha` | String! | SHA1 ID of the commit | +| `title` | String | Title of the commit message | +| `description` | String | Description of the commit message | +| `message` | String | Raw commit message | +| `authoredDate` | Time | Timestamp of when the commit was authored | +| `webUrl` | String! | Web URL of the commit | +| `signatureHtml` | String | Rendered HTML of the commit signature | +| `author` | User | Author of the commit | +| `latestPipeline` | Pipeline | Latest pipeline of the commit | ### CreateDiffNotePayload @@ -226,30 +226,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | -| `iid` | ID! | | -| `title` | String! | | +| `iid` | ID! | Internal ID of the issue | +| `title` | String! | Title of the issue | | `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` | -| `state` | IssueState! | | -| `reference` | String! | | -| `author` | User! | | -| `milestone` | Milestone | | -| `dueDate` | Time | | -| `confidential` | Boolean! | | -| `discussionLocked` | Boolean! | | -| `upvotes` | Int! | | -| `downvotes` | Int! | | -| `userNotesCount` | Int! | | -| `webPath` | String! | | -| `webUrl` | String! | | -| `relativePosition` | Int | | -| `timeEstimate` | Int! | The time estimate on the issue | +| `state` | IssueState! | State of the issue | +| `reference` | String! | Internal reference of the issue. Returned in shortened format by default | +| `author` | User! | User that created the issue | +| `milestone` | Milestone | Milestone of the issue | +| `dueDate` | Time | Due date of the issue | +| `confidential` | Boolean! | Indicates the issue is confidential | +| `discussionLocked` | Boolean! | Indicates discussion is locked on the issue | +| `upvotes` | Int! | Number of upvotes the issue has received | +| `downvotes` | Int! | Number of downvotes the issue has received | +| `userNotesCount` | Int! | Number of user notes of the issue | +| `webPath` | String! | Web path of the issue | +| `webUrl` | String! | Web URL of the issue | +| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) | +| `timeEstimate` | Int! | Time estimate of the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue | -| `closedAt` | Time | | -| `createdAt` | Time! | | -| `updatedAt` | Time! | | -| `taskCompletionStatus` | TaskCompletionStatus! | | +| `closedAt` | Time | Timestamp of when the issue was closed | +| `createdAt` | Time! | Timestamp of when the issue was created | +| `updatedAt` | Time! | Timestamp of when the issue was last updated | +| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue | | `epic` | Epic | The epic to which issue belongs | | `weight` | Int | | | `designs` | DesignCollection | | @@ -283,30 +283,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | -| `iid` | ID! | | -| `title` | String! | | +| `iid` | ID! | Internal ID of the issue | +| `title` | String! | Title of the issue | | `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` | -| `state` | IssueState! | | -| `reference` | String! | | -| `author` | User! | | -| `milestone` | Milestone | | -| `dueDate` | Time | | -| `confidential` | Boolean! | | -| `discussionLocked` | Boolean! | | -| `upvotes` | Int! | | -| `downvotes` | Int! | | -| `userNotesCount` | Int! | | -| `webPath` | String! | | -| `webUrl` | String! | | -| `relativePosition` | Int | | -| `timeEstimate` | Int! | The time estimate on the issue | +| `state` | IssueState! | State of the issue | +| `reference` | String! | Internal reference of the issue. Returned in shortened format by default | +| `author` | User! | User that created the issue | +| `milestone` | Milestone | Milestone of the issue | +| `dueDate` | Time | Due date of the issue | +| `confidential` | Boolean! | Indicates the issue is confidential | +| `discussionLocked` | Boolean! | Indicates discussion is locked on the issue | +| `upvotes` | Int! | Number of upvotes the issue has received | +| `downvotes` | Int! | Number of downvotes the issue has received | +| `userNotesCount` | Int! | Number of user notes of the issue | +| `webPath` | String! | Web path of the issue | +| `webUrl` | String! | Web URL of the issue | +| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) | +| `timeEstimate` | Int! | Time estimate of the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue | -| `closedAt` | Time | | -| `createdAt` | Time! | | -| `updatedAt` | Time! | | -| `taskCompletionStatus` | TaskCompletionStatus! | | +| `closedAt` | Time | Timestamp of when the issue was closed | +| `createdAt` | Time! | Timestamp of when the issue was created | +| `updatedAt` | Time! | Timestamp of when the issue was last updated | +| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue | | `epic` | Epic | The epic to which issue belongs | | `weight` | Int | | | `designs` | DesignCollection | | @@ -317,21 +317,21 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `id` | ID! | | -| `name` | String! | | -| `path` | String! | | -| `fullName` | String! | | -| `fullPath` | ID! | | -| `description` | String | | +| `id` | ID! | ID of the namespace | +| `name` | String! | Name of the namespace | +| `path` | String! | Path of the namespace | +| `fullName` | String! | Full name of the namespace | +| `fullPath` | ID! | Full path of the namespace | +| `description` | String | Description of the namespace | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | -| `visibility` | String | | -| `lfsEnabled` | Boolean | | -| `requestAccessEnabled` | Boolean | | -| `rootStorageStatistics` | RootStorageStatistics | The aggregated storage statistics. Only available for root namespaces | +| `visibility` | String | Visibility of the namespace | +| `lfsEnabled` | Boolean | Indicates if Large File Storage (LFS) is enabled for namespace | +| `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace | +| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces | | `userPermissions` | GroupPermissions! | Permissions for the current user on the resource | -| `webUrl` | String! | | -| `avatarUrl` | String | | -| `parent` | Group | | +| `webUrl` | String! | Web URL of the group | +| `avatarUrl` | String | Avatar URL of the group | +| `parent` | Group | Parent group | | `epicsEnabled` | Boolean | | | `epic` | Epic | | @@ -346,30 +346,30 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | | `userPermissions` | IssuePermissions! | Permissions for the current user on the resource | -| `iid` | ID! | | -| `title` | String! | | +| `iid` | ID! | Internal ID of the issue | +| `title` | String! | Title of the issue | | `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` | -| `state` | IssueState! | | -| `reference` | String! | | -| `author` | User! | | -| `milestone` | Milestone | | -| `dueDate` | Time | | -| `confidential` | Boolean! | | -| `discussionLocked` | Boolean! | | -| `upvotes` | Int! | | -| `downvotes` | Int! | | -| `userNotesCount` | Int! | | -| `webPath` | String! | | -| `webUrl` | String! | | -| `relativePosition` | Int | | -| `timeEstimate` | Int! | The time estimate on the issue | +| `state` | IssueState! | State of the issue | +| `reference` | String! | Internal reference of the issue. Returned in shortened format by default | +| `author` | User! | User that created the issue | +| `milestone` | Milestone | Milestone of the issue | +| `dueDate` | Time | Due date of the issue | +| `confidential` | Boolean! | Indicates the issue is confidential | +| `discussionLocked` | Boolean! | Indicates discussion is locked on the issue | +| `upvotes` | Int! | Number of upvotes the issue has received | +| `downvotes` | Int! | Number of downvotes the issue has received | +| `userNotesCount` | Int! | Number of user notes of the issue | +| `webPath` | String! | Web path of the issue | +| `webUrl` | String! | Web URL of the issue | +| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) | +| `timeEstimate` | Int! | Time estimate of the issue | | `totalTimeSpent` | Int! | Total time reported as spent on the issue | -| `closedAt` | Time | | -| `createdAt` | Time! | | -| `updatedAt` | Time! | | -| `taskCompletionStatus` | TaskCompletionStatus! | | +| `closedAt` | Time | Timestamp of when the issue was closed | +| `createdAt` | Time! | Timestamp of when the issue was created | +| `updatedAt` | Time! | Timestamp of when the issue was last updated | +| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue | | `epic` | Epic | The epic to which issue belongs | | `weight` | Int | | | `designs` | DesignCollection | | @@ -392,65 +392,65 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | 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` | -| `title` | String! | | -| `color` | String! | | -| `textColor` | String! | | +| `title` | String! | Content of the label | +| `color` | String! | Background color of the label | +| `textColor` | String! | Text color of the label | ### MergeRequest | Name | Type | Description | | --- | ---- | ---------- | | `userPermissions` | MergeRequestPermissions! | Permissions for the current user on the resource | -| `id` | ID! | | -| `iid` | String! | | -| `title` | String! | | +| `id` | ID! | ID of the merge request | +| `iid` | String! | Internal ID of the merge request | +| `title` | String! | Title of the merge request | | `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` | -| `state` | MergeRequestState! | | -| `createdAt` | Time! | | -| `updatedAt` | Time! | | -| `sourceProject` | Project | | -| `targetProject` | Project! | | -| `diffRefs` | DiffRefs | | -| `project` | Project! | | -| `projectId` | Int! | | -| `sourceProjectId` | Int | | -| `targetProjectId` | Int! | | -| `sourceBranch` | String! | | -| `targetBranch` | String! | | -| `workInProgress` | Boolean! | | -| `mergeWhenPipelineSucceeds` | Boolean | | -| `diffHeadSha` | String | | -| `mergeCommitSha` | String | | -| `userNotesCount` | Int | | -| `shouldRemoveSourceBranch` | Boolean | | -| `forceRemoveSourceBranch` | Boolean | | -| `mergeStatus` | String | | -| `inProgressMergeCommitSha` | String | | -| `mergeError` | String | | -| `allowCollaboration` | Boolean | | -| `shouldBeRebased` | Boolean! | | -| `rebaseCommitSha` | String | | -| `rebaseInProgress` | Boolean! | | -| `mergeCommitMessage` | String | | -| `defaultMergeCommitMessage` | String | | -| `mergeOngoing` | Boolean! | | -| `sourceBranchExists` | Boolean! | | -| `mergeableDiscussionsState` | Boolean | | -| `webUrl` | String | | -| `upvotes` | Int! | | -| `downvotes` | Int! | | -| `headPipeline` | Pipeline | | -| `milestone` | Milestone | The milestone this merge request is linked to | -| `subscribed` | Boolean! | Boolean flag for whether the currently logged in user is subscribed to this MR | -| `discussionLocked` | Boolean! | Boolean flag determining if comments on the merge request are locked to members only | -| `timeEstimate` | Int! | The time estimate for the merge request | +| `state` | MergeRequestState! | State of the merge request | +| `createdAt` | Time! | Timestamp of when the merge request was created | +| `updatedAt` | Time! | Timestamp of when the merge request was last updated | +| `sourceProject` | Project | Source project of the merge request | +| `targetProject` | Project! | Target project of the merge request | +| `diffRefs` | DiffRefs | References of the base SHA, the head SHA, and the start SHA for this merge request | +| `project` | Project! | Alias for target_project | +| `projectId` | Int! | ID of the merge request project | +| `sourceProjectId` | Int | ID of the merge request source project | +| `targetProjectId` | Int! | ID of the merge request target project | +| `sourceBranch` | String! | Source branch of the merge request | +| `targetBranch` | String! | Target branch of the merge request | +| `workInProgress` | Boolean! | Indicates if the merge request is a work in progress (WIP) | +| `mergeWhenPipelineSucceeds` | Boolean | Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS) | +| `diffHeadSha` | String | Diff head SHA of the merge request | +| `mergeCommitSha` | String | SHA of the merge request commit (set once merged) | +| `userNotesCount` | Int | User notes count of the merge request | +| `shouldRemoveSourceBranch` | Boolean | Indicates if the source branch of the merge request will be deleted after merge | +| `forceRemoveSourceBranch` | Boolean | Indicates if the project settings will lead to source branch deletion after merge | +| `mergeStatus` | String | Status of the merge request | +| `inProgressMergeCommitSha` | String | Commit SHA of the merge request if merge is in progress | +| `mergeError` | String | Error message due to a merge error | +| `allowCollaboration` | Boolean | Indicates if members of the target project can push to the fork | +| `shouldBeRebased` | Boolean! | Indicates if the merge request will be rebased | +| `rebaseCommitSha` | String | Rebase commit SHA of the merge request | +| `rebaseInProgress` | Boolean! | Indicates if there is a rebase currently in progress for the merge request | +| `mergeCommitMessage` | String | Deprecated - renamed to defaultMergeCommitMessage | +| `defaultMergeCommitMessage` | String | Default merge commit message of the merge request | +| `mergeOngoing` | Boolean! | Indicates if a merge is currently occurring | +| `sourceBranchExists` | Boolean! | Indicates if the source branch of the merge request exists | +| `mergeableDiscussionsState` | Boolean | Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged | +| `webUrl` | String | Web URL of the merge request | +| `upvotes` | Int! | Number of upvotes for the merge request | +| `downvotes` | Int! | Number of downvotes for the merge request | +| `headPipeline` | Pipeline | The pipeline running on the branch HEAD of the merge request | +| `milestone` | Milestone | The milestone of the merge request | +| `subscribed` | Boolean! | Indicates if the currently logged in user is subscribed to this merge request | +| `discussionLocked` | Boolean! | Indicates if comments on the merge request are locked to members only | +| `timeEstimate` | Int! | Time estimate of 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 | -| `taskCompletionStatus` | TaskCompletionStatus! | | +| `reference` | String! | Internal reference of the merge request. Returned in shortened format by default | +| `taskCompletionStatus` | TaskCompletionStatus! | Completion status of tasks | ### MergeRequestPermissions @@ -477,36 +477,36 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `version` | String! | | -| `revision` | String! | | +| `version` | String! | Version | +| `revision` | String! | Revision | ### Milestone | Name | Type | Description | | --- | ---- | ---------- | -| `description` | String | | -| `title` | String! | | -| `state` | String! | | -| `dueDate` | Time | | -| `startDate` | Time | | -| `createdAt` | Time! | | -| `updatedAt` | Time! | | +| `description` | String | Description of the milestone | +| `title` | String! | Title of the milestone | +| `state` | String! | State of the milestone | +| `dueDate` | Time | Timestamp of the milestone due date | +| `startDate` | Time | Timestamp of the milestone start date | +| `createdAt` | Time! | Timestamp of milestone creation | +| `updatedAt` | Time! | Timestamp of last milestone update | ### Namespace | Name | Type | Description | | --- | ---- | ---------- | -| `id` | ID! | | -| `name` | String! | | -| `path` | String! | | -| `fullName` | String! | | -| `fullPath` | ID! | | -| `description` | String | | +| `id` | ID! | ID of the namespace | +| `name` | String! | Name of the namespace | +| `path` | String! | Path of the namespace | +| `fullName` | String! | Full name of the namespace | +| `fullPath` | ID! | Full path of the namespace | +| `description` | String | Description of the namespace | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | -| `visibility` | String | | -| `lfsEnabled` | Boolean | | -| `requestAccessEnabled` | Boolean | | -| `rootStorageStatistics` | RootStorageStatistics | The aggregated storage statistics. Only available for root namespaces | +| `visibility` | String | Visibility of the namespace | +| `lfsEnabled` | Boolean | Indicates if Large File Storage (LFS) is enabled for namespace | +| `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace | +| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces | ### Note @@ -578,47 +578,47 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | | `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource | -| `id` | ID! | | -| `fullPath` | ID! | | -| `path` | String! | | -| `nameWithNamespace` | String! | | -| `name` | String! | | -| `description` | String | | +| `id` | ID! | ID of the project | +| `fullPath` | ID! | Full path of the project | +| `path` | String! | Path of the project | +| `nameWithNamespace` | String! | Full name of the project with its namespace | +| `name` | String! | Name of the project (without namespace) | +| `description` | String | Short description of the project | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | -| `tagList` | String | | -| `sshUrlToRepo` | String | | -| `httpUrlToRepo` | String | | -| `webUrl` | String | | -| `starCount` | Int! | | -| `forksCount` | Int! | | -| `createdAt` | Time | | -| `lastActivityAt` | Time | | -| `archived` | Boolean | | -| `visibility` | String | | -| `containerRegistryEnabled` | Boolean | | -| `sharedRunnersEnabled` | Boolean | | -| `lfsEnabled` | Boolean | | -| `mergeRequestsFfOnlyEnabled` | Boolean | | -| `avatarUrl` | String | | -| `issuesEnabled` | Boolean | | -| `mergeRequestsEnabled` | Boolean | | -| `wikiEnabled` | Boolean | | -| `snippetsEnabled` | Boolean | | -| `jobsEnabled` | Boolean | | -| `publicJobs` | Boolean | | -| `openIssuesCount` | Int | | -| `importStatus` | String | | -| `onlyAllowMergeIfPipelineSucceeds` | Boolean | | -| `requestAccessEnabled` | Boolean | | -| `onlyAllowMergeIfAllDiscussionsAreResolved` | Boolean | | -| `printingMergeRequestLinkEnabled` | Boolean | | -| `removeSourceBranchAfterMerge` | Boolean | Remove the source branch by default after merge | -| `namespace` | Namespace | | -| `group` | Group | | -| `statistics` | ProjectStatistics | | -| `repository` | Repository | | -| `mergeRequest` | MergeRequest | | -| `issue` | ExtendedIssue | | +| `tagList` | String | List of project tags | +| `sshUrlToRepo` | String | URL to connect to the project via SSH | +| `httpUrlToRepo` | String | URL to connect to the project via HTTPS | +| `webUrl` | String | Web URL of the project | +| `starCount` | Int! | Number of times the project has been starred | +| `forksCount` | Int! | Number of times the project has been forked | +| `createdAt` | Time | Timestamp of the project creation | +| `lastActivityAt` | Time | Timestamp of the project last activity | +| `archived` | Boolean | Archived status of the project | +| `visibility` | String | Visibility of the project | +| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry | +| `sharedRunnersEnabled` | Boolean | Indicates if shared runners are enabled on the project | +| `lfsEnabled` | Boolean | Indicates if the project has Large File Storage (LFS) enabled | +| `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 | URL to avatar image file of the project | +| `issuesEnabled` | Boolean | (deprecated) Does this project have issues enabled?. Use `issues_access_level` instead | +| `mergeRequestsEnabled` | Boolean | (deprecated) Does this project have merge_requests enabled?. Use `merge_requests_access_level` instead | +| `wikiEnabled` | Boolean | (deprecated) Does this project have wiki enabled?. Use `wiki_access_level` instead | +| `snippetsEnabled` | Boolean | (deprecated) Does this project have snippets enabled?. Use `snippets_access_level` instead | +| `jobsEnabled` | Boolean | (deprecated) Enable jobs for this project. Use `builds_access_level` instead | +| `publicJobs` | Boolean | Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts | +| `openIssuesCount` | Int | Number of open issues for the project | +| `importStatus` | String | Status of project import background job of the project | +| `onlyAllowMergeIfPipelineSucceeds` | Boolean | Indicates if merge requests of the project can only be merged with successful jobs | +| `requestAccessEnabled` | Boolean | Indicates if users can request member access to the project | +| `onlyAllowMergeIfAllDiscussionsAreResolved` | Boolean | Indicates if merge requests of the project can only be merged when all the discussions are resolved | +| `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 | Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project | +| `namespace` | Namespace | Namespace of the project | +| `group` | Group | Group of the project | +| `statistics` | ProjectStatistics | Statistics of the project | +| `repository` | Repository | Git repository of the project | +| `mergeRequest` | MergeRequest | A single merge request of the project | +| `issue` | ExtendedIssue | A single issue of the project | ### ProjectPermissions @@ -670,13 +670,13 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `commitCount` | Int! | | -| `storageSize` | Int! | | -| `repositorySize` | Int! | | -| `lfsObjectsSize` | Int! | | -| `buildArtifactsSize` | Int! | | -| `packagesSize` | Int! | | -| `wikiSize` | Int | | +| `commitCount` | Int! | Commit count of the project | +| `storageSize` | Int! | Storage size of the project | +| `repositorySize` | Int! | Repository size of the project | +| `lfsObjectsSize` | Int! | Large File Storage (LFS) object size of the project | +| `buildArtifactsSize` | Int! | Build artifacts size of the project | +| `packagesSize` | Int! | Packages size of the project | +| `wikiSize` | Int | Wiki size of the project | ### RemoveAwardEmojiPayload @@ -690,10 +690,10 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `rootRef` | String | | -| `empty` | Boolean! | | -| `exists` | Boolean! | | -| `tree` | Tree | | +| `rootRef` | String | Default branch of the repository | +| `empty` | Boolean! | Indicates repository has no visible content | +| `exists` | Boolean! | Indicates a corresponding Git repository exists on disk | +| `tree` | Tree | Tree of the repository | ### RootStorageStatistics @@ -722,8 +722,8 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `count` | Int! | | -| `completedCount` | Int! | | +| `count` | Int! | Number of total tasks | +| `completedCount` | Int! | Number of completed tasks | ### Todo @@ -785,7 +785,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph | Name | Type | Description | | --- | ---- | ---------- | -| `name` | String! | | -| `username` | String! | | -| `avatarUrl` | String! | | -| `webUrl` | String! | | +| `name` | String! | Human-readable name of the user | +| `username` | String! | Username of the user. Unique within this instance of GitLab | +| `avatarUrl` | String! | URL of the user's avatar | +| `webUrl` | String! | Web URL of the user | diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb index f22fc41a6d8..0e7e0c40a8a 100644 --- a/lib/gitlab/danger/helper.rb +++ b/lib/gitlab/danger/helper.rb @@ -93,8 +93,8 @@ module Gitlab docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now. none: "", qa: "~QA", - test: "~test for `spec/features/*`", - engineering_productivity: "Engineering Productivity for CI config review" + test: "~test ~Quality for `spec/features/*`", + engineering_productivity: '~"Engineering Productivity" for CI, Danger' }.freeze CATEGORIES = { %r{\Adoc/} => :none, # To reinstate roulette for documentation, set to `:docs`. @@ -104,7 +104,7 @@ module Gitlab %r{\A(ee/)?public/} => :frontend, %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend, %r{\A(ee/)?vendor/assets/} => :frontend, - %r{\Ascripts/frontend/} => :frontend, + %r{\A(ee/)?scripts/frontend/} => :frontend, %r{(\A|/)( \.babelrc | \.eslintignore | @@ -130,14 +130,18 @@ module Gitlab %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database, %r{\Arubocop/cop/migration(/|\.rb)} => :database, + %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity, + %r{Dangerfile\z} => :engineering_productivity, + %r{\A(ee/)?(danger/|lib/gitlab/danger/)} => :engineering_productivity, + %r{\A(ee/)?scripts/} => :engineering_productivity, + %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend, - %r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend, + %r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend, %r{\A(ee/)?spec/features/} => :test, %r{\A(ee/)?spec/(?!javascripts|frontend)[^/]+} => :backend, %r{\A(ee/)?vendor/(?!assets)[^/]+} => :backend, %r{\A(ee/)?vendor/(languages\.yml|licenses\.csv)\z} => :backend, - %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity, - %r{\A(Dangerfile|Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend, + %r{\A(Gemfile|Gemfile.lock|Procfile|Rakefile)\z} => :backend, %r{\A[A-Z_]+_VERSION\z} => :backend, %r{\A\.rubocop(_todo)?\.yml\z} => :backend, diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb index 5c2324836d7..e96f5177195 100644 --- a/lib/gitlab/danger/teammate.rb +++ b/lib/gitlab/danger/teammate.rb @@ -67,7 +67,10 @@ module Gitlab area && labels.any?("devops::#{area.downcase}") if kind == :reviewer when :engineering_productivity - role[/Engineering Productivity/] if kind == :reviewer + return false unless role[/Engineering Productivity/] + return true if kind == :reviewer + + capabilities(project).include?("#{kind} backend") else capabilities(project).include?("#{kind} #{category}") end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8bd33a223ee..76bfa60867d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2898,6 +2898,9 @@ msgstr "" msgid "Change assignee(s)." msgstr "" +msgid "Change branches" +msgstr "" + msgid "Change label" msgstr "" @@ -7397,6 +7400,9 @@ msgstr "" msgid "From %{providerTitle}" msgstr "" +msgid "From %{source_title} into" +msgstr "" + msgid "From Bitbucket" msgstr "" @@ -10287,6 +10293,9 @@ msgstr "" msgid "Merge in progress" msgstr "" +msgid "Merge options" +msgstr "" + msgid "Merge request" msgstr "" @@ -14871,6 +14880,9 @@ msgstr "" msgid "Select an existing Kubernetes cluster or create a new one" msgstr "" +msgid "Select branch" +msgstr "" + msgid "Select branch/tag" msgstr "" diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 973d5a2dcfc..f10cdf6da1e 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -216,8 +216,7 @@ describe 'Dashboard Projects' do expect(page).to have_selector('.merge-request-form') expect(current_path).to eq project_new_merge_request_path(project) expect(find('#merge_request_target_project_id', visible: false).value).to eq project.id.to_s - expect(find('input#merge_request_source_branch', visible: false).value).to eq 'feature' - expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master' + expect(page).to have_content "From feature into master" end end diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb index 81c56855961..821db8a1d5b 100644 --- a/spec/features/merge_request/user_edits_merge_request_spec.rb +++ b/spec/features/merge_request/user_edits_merge_request_spec.rb @@ -17,7 +17,7 @@ describe 'User edits a merge request', :js do end it 'changes the target branch' do - expect(page).to have_content('Target branch') + expect(page).to have_content('From master into feature') select2('merge-test', from: '#merge_request_target_branch') click_button('Save changes') diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb index 46a6f62ba14..34b15aeaa25 100644 --- a/spec/features/projects/commit/cherry_pick_spec.rb +++ b/spec/features/projects/commit/cherry_pick_spec.rb @@ -55,12 +55,16 @@ describe 'Cherry-pick Commits' do end end - context "I cherry-pick a commit in a new merge request" do + context "I cherry-pick a commit in a new merge request", :js do it do + find('.header-action-buttons a.dropdown-toggle').click find("a[href='#modal-cherry-pick-commit']").click page.within('#modal-cherry-pick-commit') do click_button 'Cherry-pick' end + + wait_for_requests + expect(page).to have_content("The commit has been successfully cherry-picked into cherry-pick-#{master_pickable_commit.short_id}. You can now submit a merge request to get this change into the original branch.") expect(page).to have_content("From cherry-pick-#{master_pickable_commit.short_id} into master") end diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js new file mode 100644 index 00000000000..38ffe98c79b --- /dev/null +++ b/spec/frontend/pipelines/graph/action_component_spec.js @@ -0,0 +1,75 @@ +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import axios from '~/lib/utils/axios_utils'; +import ActionComponent from '~/pipelines/components/graph/action_component.vue'; + +describe('pipeline graph action component', () => { + let wrapper; + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + + mock.onPost('foo.json').reply(200); + + wrapper = mount(ActionComponent, { + propsData: { + tooltipText: 'bar', + link: 'foo', + actionIcon: 'cancel', + }, + sync: false, + }); + }); + + afterEach(() => { + mock.restore(); + wrapper.destroy(); + }); + + it('should render the provided title as a bootstrap tooltip', () => { + expect(wrapper.attributes('data-original-title')).toBe('bar'); + }); + + it('should update bootstrap tooltip when title changes', done => { + wrapper.setProps({ tooltipText: 'changed' }); + + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.attributes('data-original-title')).toBe('changed'); + }) + .then(done) + .catch(done.fail); + }); + + it('should render an svg', () => { + expect(wrapper.find('.ci-action-icon-wrapper')).toBeDefined(); + expect(wrapper.find('svg')).toBeDefined(); + }); + + describe('on click', () => { + it('emits `pipelineActionRequestComplete` after a successful request', done => { + jest.spyOn(wrapper.vm, '$emit'); + + wrapper.find('button').trigger('click'); + + waitForPromises() + .then(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); + done(); + }) + .catch(done.fail); + }); + + it('renders a loading icon while waiting for request', done => { + wrapper.find('button').trigger('click'); + + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js index 8cf290f2663..45ac278dd38 100644 --- a/spec/javascripts/pipelines/pipeline_triggerer_spec.js +++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js @@ -17,6 +17,7 @@ describe('Pipelines Triggerer', () => { const createComponent = () => { wrapper = mount(pipelineTriggerer, { propsData: mockData, + sync: false, }); }; @@ -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'); + }); }); }); diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js index d47504d2f54..1c785ec6ffe 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/frontend/pipelines/pipelines_table_row_spec.js @@ -1,22 +1,21 @@ -import Vue from 'vue'; -import tableRowComp from '~/pipelines/components/pipelines_table_row.vue'; +import { mount } from '@vue/test-utils'; +import PipelinesTableRowComponent from '~/pipelines/components/pipelines_table_row.vue'; import eventHub from '~/pipelines/event_hub'; describe('Pipelines Table Row', () => { const jsonFixtureName = 'pipelines/pipelines.json'; - const buildComponent = pipeline => { - const PipelinesTableRowComponent = Vue.extend(tableRowComp); - return new PipelinesTableRowComponent({ - el: document.querySelector('.test-dom-element'), + + const createWrapper = pipeline => + mount(PipelinesTableRowComponent, { propsData: { pipeline, autoDevopsHelpPath: 'foo', viewType: 'root', }, - }).$mount(); - }; + sync: false, + }); - let component; + let wrapper; let pipeline; let pipelineWithoutAuthor; let pipelineWithoutCommit; @@ -32,28 +31,29 @@ describe('Pipelines Table Row', () => { }); afterEach(() => { - component.$destroy(); + wrapper.destroy(); + wrapper = null; }); it('should render a table row', () => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); - expect(component.$el.getAttribute('class')).toContain('gl-responsive-table-row'); + expect(wrapper.attributes('class')).toContain('gl-responsive-table-row'); }); describe('status column', () => { beforeEach(() => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); }); it('should render a pipeline link', () => { - expect( - component.$el.querySelector('.table-section.commit-link a').getAttribute('href'), - ).toEqual(pipeline.path); + expect(wrapper.find('.table-section.commit-link a').attributes('href')).toEqual( + pipeline.path, + ); }); it('should render status text', () => { - expect(component.$el.querySelector('.table-section.commit-link a').textContent).toContain( + expect(wrapper.find('.table-section.commit-link a').text()).toContain( pipeline.details.status.text, ); }); @@ -61,33 +61,32 @@ describe('Pipelines Table Row', () => { describe('information column', () => { beforeEach(() => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); }); it('should render a pipeline link', () => { - expect( - component.$el.querySelector('.table-section:nth-child(2) a').getAttribute('href'), - ).toEqual(pipeline.path); + expect(wrapper.find('.table-section:nth-child(2) a').attributes('href')).toEqual( + pipeline.path, + ); }); it('should render pipeline ID', () => { - expect( - component.$el.querySelector('.table-section:nth-child(2) a > span').textContent, - ).toEqual(`#${pipeline.id}`); + expect(wrapper.find('.table-section:nth-child(2) a > span').text()).toEqual( + `#${pipeline.id}`, + ); }); describe('when a user is provided', () => { it('should render user information', () => { expect( - component.$el - .querySelector('.table-section:nth-child(3) .js-pipeline-url-user') - .getAttribute('href'), + wrapper.find('.table-section:nth-child(3) .js-pipeline-url-user').attributes('href'), ).toEqual(pipeline.user.path); expect( - component.$el - .querySelector('.table-section:nth-child(3) .js-user-avatar-image-toolip') - .textContent.trim(), + wrapper + .find('.table-section:nth-child(3) .js-user-avatar-image-toolip') + .text() + .trim(), ).toEqual(pipeline.user.name); }); }); @@ -95,40 +94,47 @@ describe('Pipelines Table Row', () => { describe('commit column', () => { it('should render link to commit', () => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); - const commitLink = component.$el.querySelector('.branch-commit .commit-sha'); + const commitLink = wrapper.find('.branch-commit .commit-sha'); - expect(commitLink.getAttribute('href')).toEqual(pipeline.commit.commit_path); + expect(commitLink.attributes('href')).toEqual(pipeline.commit.commit_path); }); const findElements = () => { - const commitTitleElement = component.$el.querySelector('.branch-commit .commit-title'); - const commitAuthorElement = commitTitleElement.querySelector('a.avatar-image-container'); + const commitTitleElement = wrapper.find('.branch-commit .commit-title'); + const commitAuthorElement = commitTitleElement.find('a.avatar-image-container'); - if (!commitAuthorElement) { - return { commitAuthorElement }; + if (!commitAuthorElement.exists()) { + return { + commitAuthorElement, + }; } - const commitAuthorLink = commitAuthorElement.getAttribute('href'); + const commitAuthorLink = commitAuthorElement.attributes('href'); const commitAuthorName = commitAuthorElement - .querySelector('.js-user-avatar-image-toolip') - .textContent.trim(); - - return { commitAuthorElement, commitAuthorLink, commitAuthorName }; + .find('.js-user-avatar-image-toolip') + .text() + .trim(); + + return { + commitAuthorElement, + commitAuthorLink, + commitAuthorName, + }; }; it('renders nothing without commit', () => { expect(pipelineWithoutCommit.commit).toBe(null); - component = buildComponent(pipelineWithoutCommit); + wrapper = createWrapper(pipelineWithoutCommit); const { commitAuthorElement } = findElements(); - expect(commitAuthorElement).toBe(null); + expect(commitAuthorElement.exists()).toBe(false); }); it('renders commit author', () => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); const { commitAuthorLink, commitAuthorName } = findElements(); expect(commitAuthorLink).toEqual(pipeline.commit.author.path); @@ -137,8 +143,8 @@ describe('Pipelines Table Row', () => { it('renders commit with unregistered author', () => { expect(pipelineWithoutAuthor.commit.author).toBe(null); - component = buildComponent(pipelineWithoutAuthor); + wrapper = createWrapper(pipelineWithoutAuthor); const { commitAuthorLink, commitAuthorName } = findElements(); expect(commitAuthorLink).toEqual(`mailto:${pipelineWithoutAuthor.commit.author_email}`); @@ -148,13 +154,12 @@ describe('Pipelines Table Row', () => { describe('stages column', () => { beforeEach(() => { - component = buildComponent(pipeline); + wrapper = createWrapper(pipeline); }); it('should render an icon for each stage', () => { expect( - component.$el.querySelectorAll('.table-section:nth-child(4) .js-builds-dropdown-button') - .length, + wrapper.findAll('.table-section:nth-child(4) .js-builds-dropdown-button').length, ).toEqual(pipeline.details.stages.length); }); }); @@ -172,44 +177,49 @@ describe('Pipelines Table Row', () => { withActions.cancel_path = '/cancel'; withActions.retry_path = '/retry'; - component = buildComponent(withActions); + wrapper = createWrapper(withActions); }); it('should render the provided actions', () => { - expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull(); - expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull(); - const dropdownMenu = component.$el.querySelectorAll('.dropdown-menu'); + expect(wrapper.find('.js-pipelines-retry-button').exists()).toBe(true); + expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true); + const dropdownMenu = wrapper.find('.dropdown-menu'); - expect(dropdownMenu).toContainText(scheduledJobAction.name); + expect(dropdownMenu.text()).toContain(scheduledJobAction.name); }); it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => { eventHub.$on('retryPipeline', endpoint => { - expect(endpoint).toEqual('/retry'); + expect(endpoint).toBe('/retry'); }); - component.$el.querySelector('.js-pipelines-retry-button').click(); - - expect(component.isRetrying).toEqual(true); + wrapper.find('.js-pipelines-retry-button').trigger('click'); + expect(wrapper.vm.isRetrying).toBe(true); }); it('emits `openConfirmationModal` event when cancel button is clicked and toggles loading', () => { eventHub.$once('openConfirmationModal', data => { const { id, ref, commit } = pipeline; - expect(data.endpoint).toEqual('/cancel'); - expect(data.pipeline).toEqual(jasmine.objectContaining({ id, ref, commit })); + expect(data.endpoint).toBe('/cancel'); + expect(data.pipeline).toEqual( + expect.objectContaining({ + id, + ref, + commit, + }), + ); }); - component.$el.querySelector('.js-pipelines-cancel-button').click(); + wrapper.find('.js-pipelines-cancel-button').trigger('click'); }); it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => { - component.cancelingPipeline = pipeline.id; - component + wrapper.setProps({ cancelingPipeline: pipeline.id }); + wrapper.vm .$nextTick() .then(() => { - expect(component.isCancelling).toEqual(true); + expect(wrapper.vm.isCancelling).toBe(true); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js deleted file mode 100644 index 321497b35b5..00000000000 --- a/spec/javascripts/pipelines/graph/action_component_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import actionComponent from '~/pipelines/components/graph/action_component.vue'; -import mountComponent from '../../helpers/vue_mount_component_helper'; - -describe('pipeline graph action component', () => { - let component; - let mock; - - beforeEach(done => { - const ActionComponent = Vue.extend(actionComponent); - mock = new MockAdapter(axios); - - mock.onPost('foo.json').reply(200); - - component = mountComponent(ActionComponent, { - tooltipText: 'bar', - link: 'foo', - actionIcon: 'cancel', - }); - - Vue.nextTick(done); - }); - - afterEach(() => { - mock.restore(); - component.$destroy(); - }); - - it('should render the provided title as a bootstrap tooltip', () => { - expect(component.$el.getAttribute('data-original-title')).toEqual('bar'); - }); - - it('should update bootstrap tooltip when title changes', done => { - component.tooltipText = 'changed'; - - component - .$nextTick() - .then(() => { - expect(component.$el.getAttribute('data-original-title')).toBe('changed'); - }) - .then(done) - .catch(done.fail); - }); - - it('should render an svg', () => { - expect(component.$el.querySelector('.ci-action-icon-wrapper')).toBeDefined(); - expect(component.$el.querySelector('svg')).toBeDefined(); - }); - - describe('on click', () => { - it('emits `pipelineActionRequestComplete` after a successful request', done => { - spyOn(component, '$emit'); - - component.$el.click(); - - setTimeout(() => { - component - .$nextTick() - .then(() => { - expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete'); - }) - .catch(done.fail); - - done(); - }, 0); - }); - - it('renders a loading icon while waiting for request', done => { - component.$el.click(); - - component.$nextTick(() => { - expect(component.$el.querySelector('.js-action-icon-loading')).not.toBeNull(); - setTimeout(() => { - done(); - }); - }); - }); - }); -}); diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 1696d3566ad..8056418e697 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -178,6 +178,7 @@ describe Gitlab::Danger::Helper do 'app/assets/foo' | :frontend 'app/views/foo' | :frontend 'public/foo' | :frontend + 'scripts/frontend/foo' | :frontend 'spec/javascripts/foo' | :frontend 'spec/frontend/bar' | :frontend 'vendor/assets/foo' | :frontend @@ -193,10 +194,8 @@ describe Gitlab::Danger::Helper do 'app/models/foo' | :backend 'bin/foo' | :backend 'config/foo' | :backend - 'danger/foo' | :backend 'lib/foo' | :backend 'rubocop/foo' | :backend - 'scripts/foo' | :backend 'spec/foo' | :backend 'spec/foo/bar' | :backend @@ -209,16 +208,24 @@ describe Gitlab::Danger::Helper do 'vendor/languages.yml' | :backend 'vendor/licenses.csv' | :backend - 'Dangerfile' | :backend 'Gemfile' | :backend 'Gemfile.lock' | :backend 'Procfile' | :backend 'Rakefile' | :backend 'FOO_VERSION' | :backend + 'Dangerfile' | :engineering_productivity + 'danger/commit_messages/Dangerfile' | :engineering_productivity + 'ee/danger/commit_messages/Dangerfile' | :engineering_productivity + 'danger/commit_messages/' | :engineering_productivity + 'ee/danger/commit_messages/' | :engineering_productivity '.gitlab-ci.yml' | :engineering_productivity '.gitlab/ci/cng.gitlab-ci.yml' | :engineering_productivity '.gitlab/ci/ee-specific-checks.gitlab-ci.yml' | :engineering_productivity + 'scripts/foo' | :engineering_productivity + 'lib/gitlab/danger/foo' | :engineering_productivity + 'ee/lib/gitlab/danger/foo' | :engineering_productivity + 'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml' | :backend 'ee/FOO_VERSION' | :unknown diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb index bd1c2b10dc8..35edfa08a63 100644 --- a/spec/lib/gitlab/danger/teammate_spec.rb +++ b/spec/lib/gitlab/danger/teammate_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Danger::Teammate do expect(subject.maintainer?(project, :frontend, labels)).to be_truthy end - context 'when labels contain Create and the category is test' do + context 'when labels contain devops::create and the category is test' do let(:labels) { ['devops::create'] } context 'when role is Test Automation Engineer, Create' do @@ -79,6 +79,22 @@ describe Gitlab::Danger::Teammate do it '#maintainer? returns false' do expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey end + + context 'when capabilities include maintainer backend' do + let(:capabilities) { ['maintainer backend'] } + + it '#maintainer? returns true' do + expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy + end + end + + context 'when capabilities include trainee_maintainer backend' do + let(:capabilities) { ['trainee_maintainer backend'] } + + it '#traintainer? returns true' do + expect(subject.traintainer?(project, :engineering_productivity, labels)).to be_truthy + end + end end end end diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 1269fac7f48..b60c5cc59be 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -262,7 +262,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do it 'has the correct number of pipelines and statuses' do expect(@project.ci_pipelines.size).to eq(6) - @project.ci_pipelines.zip([0, 2, 2, 2, 2, 2]) + @project.ci_pipelines.order(:id).zip([2, 2, 2, 2, 2, 0]) .each do |(pipeline, expected_status_size)| expect(pipeline.statuses.size).to eq(expected_status_size) end |