Merge branch 'master' into sh-add-object-storage-qa
* master: (31 commits)
This commit is contained in:
		
						commit
						6232c26ac7
					
				|  | @ -739,7 +739,7 @@ karma: | ||||||
|       - chrome_debug.log |       - chrome_debug.log | ||||||
|       - coverage-javascript/ |       - coverage-javascript/ | ||||||
| 
 | 
 | ||||||
| codequality: | code_quality: | ||||||
|   <<: *dedicated-no-docs-no-db-pull-cache-job |   <<: *dedicated-no-docs-no-db-pull-cache-job | ||||||
|   image: docker:stable |   image: docker:stable | ||||||
|   allow_failure: true |   allow_failure: true | ||||||
|  | @ -757,9 +757,13 @@ codequality: | ||||||
|   script: |   script: | ||||||
|     # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products |     # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products | ||||||
|     - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') |     - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') | ||||||
|     - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code |     - docker run | ||||||
|  |         --env SOURCE_CODE="$PWD" | ||||||
|  |         --volume "$PWD":/code | ||||||
|  |         --volume /var/run/docker.sock:/var/run/docker.sock | ||||||
|  |         "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code | ||||||
|   artifacts: |   artifacts: | ||||||
|     paths: [codeclimate.json] |     paths: [gl-code-quality-report.json] | ||||||
|     expire_in: 1 week |     expire_in: 1 week | ||||||
| 
 | 
 | ||||||
| sast: | sast: | ||||||
|  |  | ||||||
|  | @ -64,11 +64,11 @@ As of July 2018, all the documentation for contributing to the GitLab project ha | ||||||
| 
 | 
 | ||||||
| ## Contribute to GitLab | ## Contribute to GitLab | ||||||
| 
 | 
 | ||||||
| For a first-time step-by-step guide to the contribution process, see |  | ||||||
| ["Contributing to GitLab"](https://about.gitlab.com/contributing/). |  | ||||||
| 
 |  | ||||||
| Thank you for your interest in contributing to GitLab. This guide details how | Thank you for your interest in contributing to GitLab. This guide details how | ||||||
| to contribute to GitLab in a way that is efficient for everyone. | to contribute to GitLab in a way that is easy for everyone. | ||||||
|  | 
 | ||||||
|  | For a first-time step-by-step guide to the contribution process, please see | ||||||
|  | ["Contributing to GitLab"](https://about.gitlab.com/contributing/). | ||||||
| 
 | 
 | ||||||
| Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute). | Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute). | ||||||
| 
 | 
 | ||||||
|  | @ -77,10 +77,10 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial | ||||||
| edition. Throughout this guide you will see references to CE and EE for | edition. Throughout this guide you will see references to CE and EE for | ||||||
| abbreviation. | abbreviation. | ||||||
| 
 | 
 | ||||||
| If you have read this guide and want to know how the GitLab [core team] | If you want to know how the GitLab [core team] | ||||||
| operates please see [the GitLab contributing process](PROCESS.md). | operates please see [the GitLab contributing process](PROCESS.md). | ||||||
| 
 | 
 | ||||||
| - [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) | [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) | ||||||
| 
 | 
 | ||||||
| ## Security vulnerability disclosure | ## Security vulnerability disclosure | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| 0.117.1 | 0.117.2 | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										5
									
								
								Gemfile
								
								
								
								
							|  | @ -214,9 +214,6 @@ gem 'jira-ruby', '~> 1.4' | ||||||
| # Flowdock integration | # Flowdock integration | ||||||
| gem 'gitlab-flowdock-git-hook', '~> 1.0.1' | gem 'gitlab-flowdock-git-hook', '~> 1.0.1' | ||||||
| 
 | 
 | ||||||
| # Gemnasium integration |  | ||||||
| gem 'gemnasium-gitlab-service', '~> 0.2' |  | ||||||
| 
 |  | ||||||
| # Slack integration | # Slack integration | ||||||
| gem 'slack-notifier', '~> 1.5.1' | gem 'slack-notifier', '~> 1.5.1' | ||||||
| 
 | 
 | ||||||
|  | @ -423,7 +420,7 @@ group :ed25519 do | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| # Gitaly GRPC client | # Gitaly GRPC client | ||||||
| gem 'gitaly-proto', '~> 0.112.0', require: 'gitaly' | gem 'gitaly-proto', '~> 0.113.0', require: 'gitaly' | ||||||
| gem 'grpc', '~> 1.11.0' | gem 'grpc', '~> 1.11.0' | ||||||
| 
 | 
 | ||||||
| # Locked until https://github.com/google/protobuf/issues/4210 is closed | # Locked until https://github.com/google/protobuf/issues/4210 is closed | ||||||
|  |  | ||||||
|  | @ -269,8 +269,6 @@ GEM | ||||||
|     fuubar (2.2.0) |     fuubar (2.2.0) | ||||||
|       rspec-core (~> 3.0) |       rspec-core (~> 3.0) | ||||||
|       ruby-progressbar (~> 1.4) |       ruby-progressbar (~> 1.4) | ||||||
|     gemnasium-gitlab-service (0.2.6) |  | ||||||
|       rugged (~> 0.21) |  | ||||||
|     gemojione (3.3.0) |     gemojione (3.3.0) | ||||||
|       json |       json | ||||||
|     get_process_mem (0.2.0) |     get_process_mem (0.2.0) | ||||||
|  | @ -284,7 +282,7 @@ GEM | ||||||
|       gettext_i18n_rails (>= 0.7.1) |       gettext_i18n_rails (>= 0.7.1) | ||||||
|       po_to_json (>= 1.0.0) |       po_to_json (>= 1.0.0) | ||||||
|       rails (>= 3.2.0) |       rails (>= 3.2.0) | ||||||
|     gitaly-proto (0.112.0) |     gitaly-proto (0.113.0) | ||||||
|       google-protobuf (~> 3.1) |       google-protobuf (~> 3.1) | ||||||
|       grpc (~> 1.10) |       grpc (~> 1.10) | ||||||
|     github-linguist (5.3.3) |     github-linguist (5.3.3) | ||||||
|  | @ -1040,12 +1038,11 @@ DEPENDENCIES | ||||||
|   font-awesome-rails (~> 4.7) |   font-awesome-rails (~> 4.7) | ||||||
|   foreman (~> 0.84.0) |   foreman (~> 0.84.0) | ||||||
|   fuubar (~> 2.2.0) |   fuubar (~> 2.2.0) | ||||||
|   gemnasium-gitlab-service (~> 0.2) |  | ||||||
|   gemojione (~> 3.3) |   gemojione (~> 3.3) | ||||||
|   gettext (~> 3.2.2) |   gettext (~> 3.2.2) | ||||||
|   gettext_i18n_rails (~> 1.8.0) |   gettext_i18n_rails (~> 1.8.0) | ||||||
|   gettext_i18n_rails_js (~> 1.3) |   gettext_i18n_rails_js (~> 1.3) | ||||||
|   gitaly-proto (~> 0.112.0) |   gitaly-proto (~> 0.113.0) | ||||||
|   github-linguist (~> 5.3.3) |   github-linguist (~> 5.3.3) | ||||||
|   gitlab-flowdock-git-hook (~> 1.0.1) |   gitlab-flowdock-git-hook (~> 1.0.1) | ||||||
|   gitlab-gollum-lib (~> 4.2) |   gitlab-gollum-lib (~> 4.2) | ||||||
|  |  | ||||||
|  | @ -272,8 +272,6 @@ GEM | ||||||
|     fuubar (2.2.0) |     fuubar (2.2.0) | ||||||
|       rspec-core (~> 3.0) |       rspec-core (~> 3.0) | ||||||
|       ruby-progressbar (~> 1.4) |       ruby-progressbar (~> 1.4) | ||||||
|     gemnasium-gitlab-service (0.2.6) |  | ||||||
|       rugged (~> 0.21) |  | ||||||
|     gemojione (3.3.0) |     gemojione (3.3.0) | ||||||
|       json |       json | ||||||
|     get_process_mem (0.2.0) |     get_process_mem (0.2.0) | ||||||
|  | @ -287,7 +285,7 @@ GEM | ||||||
|       gettext_i18n_rails (>= 0.7.1) |       gettext_i18n_rails (>= 0.7.1) | ||||||
|       po_to_json (>= 1.0.0) |       po_to_json (>= 1.0.0) | ||||||
|       rails (>= 3.2.0) |       rails (>= 3.2.0) | ||||||
|     gitaly-proto (0.112.0) |     gitaly-proto (0.113.0) | ||||||
|       google-protobuf (~> 3.1) |       google-protobuf (~> 3.1) | ||||||
|       grpc (~> 1.10) |       grpc (~> 1.10) | ||||||
|     github-linguist (5.3.3) |     github-linguist (5.3.3) | ||||||
|  | @ -1052,12 +1050,11 @@ DEPENDENCIES | ||||||
|   font-awesome-rails (~> 4.7) |   font-awesome-rails (~> 4.7) | ||||||
|   foreman (~> 0.84.0) |   foreman (~> 0.84.0) | ||||||
|   fuubar (~> 2.2.0) |   fuubar (~> 2.2.0) | ||||||
|   gemnasium-gitlab-service (~> 0.2) |  | ||||||
|   gemojione (~> 3.3) |   gemojione (~> 3.3) | ||||||
|   gettext (~> 3.2.2) |   gettext (~> 3.2.2) | ||||||
|   gettext_i18n_rails (~> 1.8.0) |   gettext_i18n_rails (~> 1.8.0) | ||||||
|   gettext_i18n_rails_js (~> 1.3) |   gettext_i18n_rails_js (~> 1.3) | ||||||
|   gitaly-proto (~> 0.112.0) |   gitaly-proto (~> 0.113.0) | ||||||
|   github-linguist (~> 5.3.3) |   github-linguist (~> 5.3.3) | ||||||
|   gitlab-flowdock-git-hook (~> 1.0.1) |   gitlab-flowdock-git-hook (~> 1.0.1) | ||||||
|   gitlab-gollum-lib (~> 4.2) |   gitlab-gollum-lib (~> 4.2) | ||||||
|  | @ -1216,4 +1213,4 @@ DEPENDENCIES | ||||||
|   wikicloth (= 0.8.1) |   wikicloth (= 0.8.1) | ||||||
| 
 | 
 | ||||||
| BUNDLED WITH | BUNDLED WITH | ||||||
|    1.16.2 |    1.16.3 | ||||||
|  |  | ||||||
|  | @ -58,7 +58,7 @@ You can access a new installation with the login **`root`** and password **`5ive | ||||||
| 
 | 
 | ||||||
| ## Contributing | ## Contributing | ||||||
| 
 | 
 | ||||||
| GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details. | GitLab is an open source project and we are very happy to accept community contributions. Please refer to [Contributing to GitLab page](https://about.gitlab.com/contributing/) for more details. | ||||||
| 
 | 
 | ||||||
| ## Licensing | ## Licensing | ||||||
| 
 | 
 | ||||||
|  | @ -66,7 +66,7 @@ GitLab Community Edition (CE) is available freely under the MIT Expat license. | ||||||
| 
 | 
 | ||||||
| All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component. | All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component. | ||||||
| 
 | 
 | ||||||
| All Documentation content that resides under the doc/ directory of this repository is licensed under Creative Commons: CC BY-SA 4.0. | All Documentation content that resides under the `doc/` directory of this repository is licensed under Creative Commons: CC BY-SA 4.0. | ||||||
| 
 | 
 | ||||||
| ## Install a development environment | ## Install a development environment | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -172,7 +172,7 @@ export default { | ||||||
| <template> | <template> | ||||||
|   <div |   <div | ||||||
|     v-if="!showEmptyState" |     v-if="!showEmptyState" | ||||||
|     class="prometheus-graphs prepend-top-10" |     class="prometheus-graphs prepend-top-default" | ||||||
|   > |   > | ||||||
|     <div class="environments d-flex align-items-center"> |     <div class="environments d-flex align-items-center"> | ||||||
|       {{ s__('Metrics|Environment') }} |       {{ s__('Metrics|Environment') }} | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ | ||||||
|     &.svg-#{$width} { |     &.svg-#{$width} { | ||||||
|       img, |       img, | ||||||
|       svg { |       svg { | ||||||
|         width: #{$width + 'px'}; |         max-width: #{$width + 'px'}; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,67 +1,39 @@ | ||||||
| class AutocompleteController < ApplicationController | class AutocompleteController < ApplicationController | ||||||
|   AWARD_EMOJI_MAX = 100 |  | ||||||
| 
 |  | ||||||
|   skip_before_action :authenticate_user!, only: [:users, :award_emojis] |   skip_before_action :authenticate_user!, only: [:users, :award_emojis] | ||||||
|   before_action :load_project, only: [:users] |  | ||||||
|   before_action :load_group, only: [:users] |  | ||||||
| 
 | 
 | ||||||
|   def users |   def users | ||||||
|     @users = AutocompleteUsersFinder.new(params: params, current_user: current_user, project: @project, group: @group).execute |     project = Autocomplete::ProjectFinder | ||||||
|  |       .new(current_user, params) | ||||||
|  |       .execute | ||||||
| 
 | 
 | ||||||
|     render json: UserSerializer.new.represent(@users) |     group = Autocomplete::GroupFinder | ||||||
|  |       .new(current_user, project, params) | ||||||
|  |       .execute | ||||||
|  | 
 | ||||||
|  |     users = Autocomplete::UsersFinder | ||||||
|  |       .new(params: params, current_user: current_user, project: project, group: group) | ||||||
|  |       .execute | ||||||
|  | 
 | ||||||
|  |     render json: UserSerializer.new.represent(users) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def user |   def user | ||||||
|     @user = User.find(params[:id]) |     user = UserFinder.new(params).execute! | ||||||
|     render json: UserSerializer.new.represent(@user) | 
 | ||||||
|  |     render json: UserSerializer.new.represent(user) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   # Displays projects to use for the dropdown when moving a resource from one | ||||||
|  |   # project to another. | ||||||
|   def projects |   def projects | ||||||
|     project = Project.find_by_id(params[:project_id]) |     projects = Autocomplete::MoveToProjectFinder | ||||||
|     projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id]) |       .new(current_user, params) | ||||||
|  |       .execute | ||||||
| 
 | 
 | ||||||
|     render json: projects.to_json(only: [:id, :name_with_namespace], methods: :name_with_namespace) |     render json: MoveToProjectSerializer.new.represent(projects) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def award_emojis |   def award_emojis | ||||||
|     emoji_with_count = AwardEmoji |     render json: AwardedEmojiFinder.new(current_user).execute | ||||||
|       .limit(AWARD_EMOJI_MAX) |  | ||||||
|       .where(user: current_user) |  | ||||||
|       .group(:name) |  | ||||||
|       .order('count_all DESC, name ASC') |  | ||||||
|       .count |  | ||||||
| 
 |  | ||||||
|     # Transform from hash to array to guarantee json order |  | ||||||
|     # e.g. { 'thumbsup' => 2, 'thumbsdown' = 1 } |  | ||||||
|     #   => [{ name: 'thumbsup' }, { name: 'thumbsdown' }] |  | ||||||
|     render json: emoji_with_count.map { |k, v| { name: k } } |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   private |  | ||||||
| 
 |  | ||||||
|   def load_group |  | ||||||
|     @group ||= begin |  | ||||||
|       if @project.blank? && params[:group_id].present? |  | ||||||
|         group = Group.find(params[:group_id]) |  | ||||||
|         return render_404 unless can?(current_user, :read_group, group) |  | ||||||
| 
 |  | ||||||
|         group |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def load_project |  | ||||||
|     @project ||= begin |  | ||||||
|       if params[:project_id].present? |  | ||||||
|         project = Project.find(params[:project_id]) |  | ||||||
|         return render_404 unless can?(current_user, :read_project, project) |  | ||||||
| 
 |  | ||||||
|         project |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def projects_finder |  | ||||||
|     MoveToProjectFinder.new(current_user) |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Autocomplete | ||||||
|  |   # Finder for retrieving a group to use for autocomplete data sources. | ||||||
|  |   class GroupFinder | ||||||
|  |     attr_reader :current_user, :project, :group_id | ||||||
|  | 
 | ||||||
|  |     # current_user - The currently logged in user, if any. | ||||||
|  |     # project - The Project (if any) to use for the autocomplete data sources. | ||||||
|  |     # params - A Hash containing parameters to use for finding the project. | ||||||
|  |     # | ||||||
|  |     # The following parameters are supported: | ||||||
|  |     # | ||||||
|  |     # * group_id: The ID of the group to find. | ||||||
|  |     def initialize(current_user = nil, project = nil, params = {}) | ||||||
|  |       @current_user = current_user | ||||||
|  |       @project = project | ||||||
|  |       @group_id = params[:group_id] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     # Attempts to find a Group based on the current group ID. | ||||||
|  |     def execute | ||||||
|  |       return unless project.blank? && group_id.present? | ||||||
|  | 
 | ||||||
|  |       group = Group.find(group_id) | ||||||
|  | 
 | ||||||
|  |       # This removes the need for using `return render_404` and similar patterns | ||||||
|  |       # in controllers that use this finder. | ||||||
|  |       unless Ability.allowed?(current_user, :read_group, group) | ||||||
|  |         raise ActiveRecord::RecordNotFound | ||||||
|  |           .new("Could not find a Group with ID #{group_id}") | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       group | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Autocomplete | ||||||
|  |   # Finder that retrieves a list of projects that an issue can be moved to. | ||||||
|  |   class MoveToProjectFinder | ||||||
|  |     attr_reader :current_user, :search, :project_id, :offset_id | ||||||
|  | 
 | ||||||
|  |     # current_user - The User object of the user that wants to view the list of | ||||||
|  |     #                projects. | ||||||
|  |     # | ||||||
|  |     # params - A Hash containing additional parameters to set. | ||||||
|  |     # | ||||||
|  |     # The following parameters can be set (as Symbols): | ||||||
|  |     # | ||||||
|  |     # * search: An optional search query to apply to the list of projects. | ||||||
|  |     # * project_id: The ID of a project to exclude from the returned relation. | ||||||
|  |     # * offset_id: The ID of a project to use for pagination. When given, only | ||||||
|  |     #   projects with a lower ID are included in the list. | ||||||
|  |     def initialize(current_user, params = {}) | ||||||
|  |       @current_user = current_user | ||||||
|  |       @search = params[:search] | ||||||
|  |       @project_id = params[:project_id] | ||||||
|  |       @offset_id = params[:offset_id] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def execute | ||||||
|  |       current_user | ||||||
|  |         .projects_where_can_admin_issues | ||||||
|  |         .optionally_search(search) | ||||||
|  |         .excluding_project(project_id) | ||||||
|  |         .paginate_in_descending_order_using_id(before: offset_id) | ||||||
|  |         .eager_load_namespace_and_owner | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Autocomplete | ||||||
|  |   # Finder for retrieving a project to use for autocomplete data sources. | ||||||
|  |   class ProjectFinder | ||||||
|  |     attr_reader :current_user, :project_id | ||||||
|  | 
 | ||||||
|  |     # current_user - The currently logged in user, if any. | ||||||
|  |     # params - A Hash containing parameters to use for finding the project. | ||||||
|  |     # | ||||||
|  |     # The following parameters are supported: | ||||||
|  |     # | ||||||
|  |     # * project_id: The ID of the project to find. | ||||||
|  |     def initialize(current_user = nil, params = {}) | ||||||
|  |       @current_user = current_user | ||||||
|  |       @project_id = params[:project_id] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     # Attempts to find a Project based on the current project ID. | ||||||
|  |     def execute | ||||||
|  |       return if project_id.blank? | ||||||
|  | 
 | ||||||
|  |       project = Project.find(project_id) | ||||||
|  | 
 | ||||||
|  |       # This removes the need for using `return render_404` and similar patterns | ||||||
|  |       # in controllers that use this finder. | ||||||
|  |       unless Ability.allowed?(current_user, :read_project, project) | ||||||
|  |         raise ActiveRecord::RecordNotFound | ||||||
|  |           .new("Could not find a Project with ID #{project_id}") | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       project | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Autocomplete | ||||||
|  |   class UsersFinder | ||||||
|  |     # The number of users to display in the results is hardcoded to 20, and | ||||||
|  |     # pagination is not supported. This ensures that performance remains | ||||||
|  |     # consistent and removes the need for implementing keyset pagination to | ||||||
|  |     # ensure good performance. | ||||||
|  |     LIMIT = 20 | ||||||
|  | 
 | ||||||
|  |     attr_reader :current_user, :project, :group, :search, :skip_users, | ||||||
|  |                 :author_id, :todo_filter, :todo_state_filter, | ||||||
|  |                 :filter_by_current_user | ||||||
|  | 
 | ||||||
|  |     def initialize(params:, current_user:, project:, group:) | ||||||
|  |       @current_user = current_user | ||||||
|  |       @project = project | ||||||
|  |       @group = group | ||||||
|  |       @search = params[:search] | ||||||
|  |       @skip_users = params[:skip_users] | ||||||
|  |       @author_id = params[:author_id] | ||||||
|  |       @todo_filter = params[:todo_filter] | ||||||
|  |       @todo_state_filter = params[:todo_state_filter] | ||||||
|  |       @filter_by_current_user = params[:current_user] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def execute | ||||||
|  |       items = limited_users | ||||||
|  | 
 | ||||||
|  |       if search.blank? | ||||||
|  |         # Include current user if available to filter by "Me" | ||||||
|  |         items.unshift(current_user) if prepend_current_user? | ||||||
|  | 
 | ||||||
|  |         if prepend_author? && (author = User.find_by_id(author_id)) | ||||||
|  |           items.unshift(author) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       items.uniq | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     private | ||||||
|  | 
 | ||||||
|  |     # Returns the users based on the input parameters, as an Array. | ||||||
|  |     # | ||||||
|  |     # This method is separate so it is easier to extend in EE. | ||||||
|  |     def limited_users | ||||||
|  |       # When changing the order of these method calls, make sure that | ||||||
|  |       # reorder_by_name() is called _before_ optionally_search(), otherwise | ||||||
|  |       # reorder_by_name will break the ORDER BY applied in optionally_search(). | ||||||
|  |       find_users | ||||||
|  |         .active | ||||||
|  |         .reorder_by_name | ||||||
|  |         .optionally_search(search) | ||||||
|  |         .where_not_in(skip_users) | ||||||
|  |         .limit_to_todo_authors( | ||||||
|  |           user: current_user, | ||||||
|  |           with_todos: todo_filter, | ||||||
|  |           todo_state: todo_state_filter | ||||||
|  |         ) | ||||||
|  |         .limit(LIMIT) | ||||||
|  |         .to_a | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def prepend_current_user? | ||||||
|  |       filter_by_current_user.present? && current_user | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def prepend_author? | ||||||
|  |       author_id.present? && current_user | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def find_users | ||||||
|  |       if project | ||||||
|  |         project.authorized_users.union_with_user(author_id) | ||||||
|  |       elsif group | ||||||
|  |         group.users_with_parents | ||||||
|  |       elsif current_user | ||||||
|  |         User.all | ||||||
|  |       else | ||||||
|  |         User.none | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| class AutocompleteUsersFinder |  | ||||||
|   # The number of users to display in the results is hardcoded to 20, and |  | ||||||
|   # pagination is not supported. This ensures that performance remains |  | ||||||
|   # consistent and removes the need for implementing keyset pagination to ensure |  | ||||||
|   # good performance. |  | ||||||
|   LIMIT = 20 |  | ||||||
| 
 |  | ||||||
|   attr_reader :current_user, :project, :group, :search, :skip_users, |  | ||||||
|               :author_id, :params |  | ||||||
| 
 |  | ||||||
|   def initialize(params:, current_user:, project:, group:) |  | ||||||
|     @current_user = current_user |  | ||||||
|     @project = project |  | ||||||
|     @group = group |  | ||||||
|     @search = params[:search] |  | ||||||
|     @skip_users = params[:skip_users] |  | ||||||
|     @author_id = params[:author_id] |  | ||||||
|     @params = params |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def execute |  | ||||||
|     items = find_users |  | ||||||
|     items = items.active |  | ||||||
|     items = items.reorder(:name) |  | ||||||
|     items = items.search(search) if search.present? |  | ||||||
|     items = items.where.not(id: skip_users) if skip_users.present? |  | ||||||
|     items = items.limit(LIMIT) |  | ||||||
| 
 |  | ||||||
|     if params[:todo_filter].present? && current_user |  | ||||||
|       items = items.todo_authors(current_user.id, params[:todo_state_filter]) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     if search.blank? |  | ||||||
|       # Include current user if available to filter by "Me" |  | ||||||
|       if params[:current_user].present? && current_user |  | ||||||
|         items = [current_user, *items].uniq |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       if author_id.present? && current_user |  | ||||||
|         author = User.find_by_id(author_id) |  | ||||||
|         items = [author, *items].uniq if author |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     items |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   private |  | ||||||
| 
 |  | ||||||
|   def find_users |  | ||||||
|     return users_from_project if project |  | ||||||
|     return group.users_with_parents if group |  | ||||||
|     return User.all if current_user |  | ||||||
| 
 |  | ||||||
|     User.none |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def users_from_project |  | ||||||
|     if author_id.present? |  | ||||||
|       union = Gitlab::SQL::Union |  | ||||||
|         .new([project.authorized_users, User.where(id: author_id)]) |  | ||||||
| 
 |  | ||||||
|       User.from("(#{union.to_sql}) #{User.table_name}") |  | ||||||
|     else |  | ||||||
|       project.authorized_users |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | # Class for retrieving information about emoji awarded _by_ a particular user. | ||||||
|  | class AwardedEmojiFinder | ||||||
|  |   attr_reader :current_user | ||||||
|  | 
 | ||||||
|  |   # current_user - The User to generate the data for. | ||||||
|  |   def initialize(current_user = nil) | ||||||
|  |     @current_user = current_user | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def execute | ||||||
|  |     return [] unless current_user | ||||||
|  | 
 | ||||||
|  |     # We want the resulting data set to be an Array containing the emoji names | ||||||
|  |     # in descending order, based on how often they were awarded. | ||||||
|  |     AwardEmoji | ||||||
|  |       .award_counts_for_user(current_user) | ||||||
|  |       .map { |name, _| { name: name } } | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1,21 +0,0 @@ | ||||||
| class MoveToProjectFinder |  | ||||||
|   PAGE_SIZE = 50 |  | ||||||
| 
 |  | ||||||
|   def initialize(user) |  | ||||||
|     @user = user |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def execute(from_project, search: nil, offset_id: nil) |  | ||||||
|     projects = @user.projects_where_can_admin_issues |  | ||||||
|     projects = projects.search(search) if search.present? |  | ||||||
|     projects = projects.excluding_project(from_project) |  | ||||||
|     projects = projects.order_id_desc |  | ||||||
| 
 |  | ||||||
|     # infinite scroll using offset |  | ||||||
|     projects = projects.where('projects.id < ?', offset_id) if offset_id.present? |  | ||||||
|     projects = projects.limit(PAGE_SIZE) |  | ||||||
| 
 |  | ||||||
|     # to ask for Project#name_with_namespace |  | ||||||
|     projects.includes(namespace: :owner) |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -0,0 +1,26 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | # A simple finding for obtaining a single User. | ||||||
|  | # | ||||||
|  | # While using `User.find_by` directly is straightforward, it can lead to a lot | ||||||
|  | # of code duplication. Sometimes we just want to find a user by an ID, other | ||||||
|  | # times we may want to exclude blocked user. By using this finder (and extending | ||||||
|  | # it whenever necessary) we can keep this logic in one place. | ||||||
|  | class UserFinder | ||||||
|  |   attr_reader :params | ||||||
|  | 
 | ||||||
|  |   def initialize(params) | ||||||
|  |     @params = params | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   # Tries to find a User, returning nil if none could be found. | ||||||
|  |   def execute | ||||||
|  |     User.find_by(id: params[:id]) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could | ||||||
|  |   # not be found. | ||||||
|  |   def execute! | ||||||
|  |     User.find(params[:id]) | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -28,6 +28,23 @@ class AwardEmoji < ActiveRecord::Base | ||||||
|         .where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids) |         .where('name IN (?) AND awardable_type = ? AND awardable_id IN (?)', [DOWNVOTE_NAME, UPVOTE_NAME], type, ids) | ||||||
|         .group('name', 'awardable_id') |         .group('name', 'awardable_id') | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     # Returns the top 100 emoji awarded by the given user. | ||||||
|  |     # | ||||||
|  |     # The returned value is a Hash mapping emoji names to the number of times | ||||||
|  |     # they were awarded: | ||||||
|  |     # | ||||||
|  |     #     { 'thumbsup' => 2, 'thumbsdown' => 1 } | ||||||
|  |     # | ||||||
|  |     # user - The User to get the awards for. | ||||||
|  |     # limt - The maximum number of emoji to return. | ||||||
|  |     def award_counts_for_user(user, limit = 100) | ||||||
|  |       limit(limit) | ||||||
|  |         .where(user: user) | ||||||
|  |         .group(:name) | ||||||
|  |         .order('count_all DESC, name ASC') | ||||||
|  |         .count | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def downvote? |   def downvote? | ||||||
|  |  | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module OptionallySearch | ||||||
|  |   extend ActiveSupport::Concern | ||||||
|  | 
 | ||||||
|  |   module ClassMethods | ||||||
|  |     def search(*) | ||||||
|  |       raise( | ||||||
|  |         NotImplementedError, | ||||||
|  |         'Your model must implement the "search" class method' | ||||||
|  |       ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     # Optionally limits a result set to those matching the given search query. | ||||||
|  |     def optionally_search(query = nil) | ||||||
|  |       query.present? ? search(query) : all | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -28,6 +28,7 @@ class Project < ActiveRecord::Base | ||||||
|   include WithUploads |   include WithUploads | ||||||
|   include BatchDestroyDependentAssociations |   include BatchDestroyDependentAssociations | ||||||
|   include FeatureGate |   include FeatureGate | ||||||
|  |   include OptionallySearch | ||||||
|   extend Gitlab::Cache::RequestCache |   extend Gitlab::Cache::RequestCache | ||||||
| 
 | 
 | ||||||
|   extend Gitlab::ConfigHelper |   extend Gitlab::ConfigHelper | ||||||
|  | @ -140,7 +141,6 @@ class Project < ActiveRecord::Base | ||||||
|   has_one :flowdock_service |   has_one :flowdock_service | ||||||
|   has_one :assembla_service |   has_one :assembla_service | ||||||
|   has_one :asana_service |   has_one :asana_service | ||||||
|   has_one :gemnasium_service |  | ||||||
|   has_one :mattermost_slash_commands_service |   has_one :mattermost_slash_commands_service | ||||||
|   has_one :mattermost_service |   has_one :mattermost_service | ||||||
|   has_one :slack_slash_commands_service |   has_one :slack_slash_commands_service | ||||||
|  | @ -384,6 +384,26 @@ class Project < ActiveRecord::Base | ||||||
|                                             only_integer: true, |                                             only_integer: true, | ||||||
|                                             message: 'needs to be beetween 10 minutes and 1 month' } |                                             message: 'needs to be beetween 10 minutes and 1 month' } | ||||||
| 
 | 
 | ||||||
|  |   # Paginates a collection using a `WHERE id < ?` condition. | ||||||
|  |   # | ||||||
|  |   # before - A project ID to use for filtering out projects with an equal or | ||||||
|  |   #      greater ID. If no ID is given, all projects are included. | ||||||
|  |   # | ||||||
|  |   # limit - The maximum number of rows to include. | ||||||
|  |   def self.paginate_in_descending_order_using_id( | ||||||
|  |     before: nil, | ||||||
|  |     limit: Kaminari.config.default_per_page | ||||||
|  |   ) | ||||||
|  |     relation = order_id_desc.limit(limit) | ||||||
|  |     relation = relation.where('projects.id < ?', before) if before | ||||||
|  | 
 | ||||||
|  |     relation | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def self.eager_load_namespace_and_owner | ||||||
|  |     includes(namespace: :owner) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   # Returns a collection of projects that is either public or visible to the |   # Returns a collection of projects that is either public or visible to the | ||||||
|   # logged in user. |   # logged in user. | ||||||
|   def self.public_or_visible_to_user(user = nil) |   def self.public_or_visible_to_user(user = nil) | ||||||
|  |  | ||||||
|  | @ -1,62 +0,0 @@ | ||||||
| # frozen_string_literal: true |  | ||||||
| 
 |  | ||||||
| require "gemnasium/gitlab_service" |  | ||||||
| 
 |  | ||||||
| class GemnasiumService < Service |  | ||||||
|   prop_accessor :token, :api_key |  | ||||||
|   validates :token, :api_key, presence: true, if: :activated? |  | ||||||
|   validate :deprecation_validation |  | ||||||
| 
 |  | ||||||
|   def title |  | ||||||
|     'Gemnasium' |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def description |  | ||||||
|     'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.' |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def self.to_param |  | ||||||
|     'gemnasium' |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def fields |  | ||||||
|     [ |  | ||||||
|       { type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ', required: true }, |  | ||||||
|       { type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com', required: true } |  | ||||||
|     ] |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def self.supported_events |  | ||||||
|     %w(push) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def deprecated? |  | ||||||
|     true |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def deprecation_message |  | ||||||
|     "Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available." |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def deprecation_validation |  | ||||||
|     errors[:base] << deprecation_message |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def execute(data) |  | ||||||
|     return unless supported_events.include?(data[:object_kind]) |  | ||||||
| 
 |  | ||||||
|     # Gitaly: this class will be removed https://gitlab.com/gitlab-org/gitlab-ee/issues/6010 |  | ||||||
|     repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do |  | ||||||
|       project.repository.path_to_repo |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     Gemnasium::GitlabService.execute( |  | ||||||
|       ref: data[:ref], |  | ||||||
|       before: data[:before], |  | ||||||
|       after: data[:after], |  | ||||||
|       token: token, |  | ||||||
|       api_key: api_key, |  | ||||||
|       repo: repo_path |  | ||||||
|     ) |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -19,6 +19,7 @@ class User < ActiveRecord::Base | ||||||
|   include BulkMemberAccessLoad |   include BulkMemberAccessLoad | ||||||
|   include BlocksJsonSerialization |   include BlocksJsonSerialization | ||||||
|   include WithUploads |   include WithUploads | ||||||
|  |   include OptionallySearch | ||||||
| 
 | 
 | ||||||
|   DEFAULT_NOTIFICATION_LEVEL = :participating |   DEFAULT_NOTIFICATION_LEVEL = :participating | ||||||
| 
 | 
 | ||||||
|  | @ -253,11 +254,41 @@ class User < ActiveRecord::Base | ||||||
|   scope :external, -> { where(external: true) } |   scope :external, -> { where(external: true) } | ||||||
|   scope :active, -> { with_state(:active).non_internal } |   scope :active, -> { with_state(:active).non_internal } | ||||||
|   scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) } |   scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) } | ||||||
|   scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } |  | ||||||
|   scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } |   scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } | ||||||
|   scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } |   scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } | ||||||
|   scope :confirmed, -> { where.not(confirmed_at: nil) } |   scope :confirmed, -> { where.not(confirmed_at: nil) } | ||||||
| 
 | 
 | ||||||
|  |   # Limits the users to those that have TODOs, optionally in the given state. | ||||||
|  |   # | ||||||
|  |   # user - The user to get the todos for. | ||||||
|  |   # | ||||||
|  |   # with_todos - If we should limit the result set to users that are the | ||||||
|  |   #              authors of todos. | ||||||
|  |   # | ||||||
|  |   # todo_state - An optional state to require the todos to be in. | ||||||
|  |   def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) | ||||||
|  |     if user && with_todos | ||||||
|  |       where(id: Todo.where(user: user, state: todo_state).select(:author_id)) | ||||||
|  |     else | ||||||
|  |       all | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   # Returns a relation that optionally includes the given user. | ||||||
|  |   # | ||||||
|  |   # user_id - The ID of the user to include. | ||||||
|  |   def self.union_with_user(user_id = nil) | ||||||
|  |     if user_id.present? | ||||||
|  |       union = Gitlab::SQL::Union.new([all, User.unscoped.where(id: user_id)]) | ||||||
|  | 
 | ||||||
|  |       # We use "unscoped" here so that any inner conditions are not repeated for | ||||||
|  |       # the outer query, which would be redundant. | ||||||
|  |       User.unscoped.from("(#{union.to_sql}) #{User.table_name}") | ||||||
|  |     else | ||||||
|  |       all | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   def self.with_two_factor_indistinct |   def self.with_two_factor_indistinct | ||||||
|     joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") |     joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") | ||||||
|       .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true) |       .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true) | ||||||
|  | @ -365,6 +396,18 @@ class User < ActiveRecord::Base | ||||||
|       ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) |       ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     # Limits the result set to users _not_ in the given query/list of IDs. | ||||||
|  |     # | ||||||
|  |     # users - The list of users to ignore. This can be an | ||||||
|  |     #         `ActiveRecord::Relation`, or an Array. | ||||||
|  |     def where_not_in(users = nil) | ||||||
|  |       users ? where.not(id: users) : all | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def reorder_by_name | ||||||
|  |       reorder(:name) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     # searches user by given pattern |     # searches user by given pattern | ||||||
|     # it compares name, email, username fields and user's secondary emails with given pattern |     # it compares name, email, username fields and user's secondary emails with given pattern | ||||||
|     # This method uses ILIKE on PostgreSQL and LIKE on MySQL. |     # This method uses ILIKE on PostgreSQL and LIKE on MySQL. | ||||||
|  |  | ||||||
|  | @ -0,0 +1,6 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class MoveToProjectEntity < Grape::Entity | ||||||
|  |   expose :id | ||||||
|  |   expose :name_with_namespace | ||||||
|  | end | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class MoveToProjectSerializer < BaseSerializer | ||||||
|  |   entity MoveToProjectEntity | ||||||
|  | end | ||||||
|  | @ -49,7 +49,7 @@ | ||||||
|         = submit_tag 'Search', class: 'btn' |         = submit_tag 'Search', class: 'btn' | ||||||
| 
 | 
 | ||||||
|     .float-right.light |     .float-right.light | ||||||
|       Runners with last contact more than a minute ago: #{@active_runners_cnt} |       Runners currently online: #{@active_runners_cnt} | ||||||
| 
 | 
 | ||||||
|   %br |   %br | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,11 +2,4 @@ | ||||||
| - page_title "Metrics for environment", @environment.name | - page_title "Metrics for environment", @environment.name | ||||||
| 
 | 
 | ||||||
| .prometheus-container{ class: container_class } | .prometheus-container{ class: container_class } | ||||||
|   .top-area |  | ||||||
|     .row |  | ||||||
|       .col-sm-6 |  | ||||||
|         %h3 |  | ||||||
|           Environment: |  | ||||||
|           = link_to @environment.name, environment_path(@environment) |  | ||||||
| 
 |  | ||||||
|   #prometheus-graphs{ data: metrics_data(@project, @environment) } |   #prometheus-graphs{ data: metrics_data(@project, @environment) } | ||||||
|  |  | ||||||
|  | @ -13,4 +13,4 @@ | ||||||
|   %h4.underlined-title Available specific runners |   %h4.underlined-title Available specific runners | ||||||
|   %ul.bordered-list.available-specific-runners |   %ul.bordered-list.available-specific-runners | ||||||
|     = render partial: 'projects/runners/runner', collection: @assignable_runners, as: :runner |     = render partial: 'projects/runners/runner', collection: @assignable_runners, as: :runner | ||||||
|   = paginate @assignable_runners, theme: "gitlab" |   = paginate @assignable_runners, theme: "gitlab", :params => { :anchor => '#js-runners-settings' } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Does not collapse runners section when using pagination | ||||||
|  | merge_request: | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: "#47845 Add failure_reason to job webhook" | ||||||
|  | merge_request: 21143 | ||||||
|  | author: matemaciek | ||||||
|  | type: added | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Fixes SVGs for empty states in job page overflowing on mobile | ||||||
|  | merge_request: | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Allow spaces in wiki markdown links when using CommonMark | ||||||
|  | merge_request: 20417 | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Fix merge requests not showing any diff files for big patches | ||||||
|  | merge_request: 21125 | ||||||
|  | author: | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Remove redundant header from metrics page | ||||||
|  | merge_request: 21282 | ||||||
|  | author: | ||||||
|  | type: changed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Remove Gemnasium service | ||||||
|  | merge_request: 21185 | ||||||
|  | author: | ||||||
|  | type: removed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: 'Events API now requires the read_user or api scope.' | ||||||
|  | merge_request: 20627 | ||||||
|  | author: Warren Parad | ||||||
|  | type: fixed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Clarify current runners online text | ||||||
|  | merge_request: 21151 | ||||||
|  | author: Ben Bodenmiller | ||||||
|  | type: other | ||||||
|  | @ -25,11 +25,11 @@ for each GitLab application server in your environment. | ||||||
|    options. Here is an example snippet to add to `/etc/fstab`: |    options. Here is an example snippet to add to `/etc/fstab`: | ||||||
| 
 | 
 | ||||||
|     ``` |     ``` | ||||||
|     10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 |     10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 | ||||||
|     10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 |     10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 | ||||||
|     10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 |     10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 | ||||||
|     10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 |     10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 | ||||||
|     10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 |     10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2 | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| 1. Create the shared directories. These may be different depending on your NFS | 1. Create the shared directories. These may be different depending on your NFS | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ Registry, etc. | ||||||
| ## Hashed Storage | ## Hashed Storage | ||||||
| 
 | 
 | ||||||
| > **Warning:** Hashed storage is in **Beta**. For the latest updates, check the | > **Warning:** Hashed storage is in **Beta**. For the latest updates, check the | ||||||
| > associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821) | > associated [issue](https://gitlab.com/gitlab-com/infrastructure/issues/3542) | ||||||
| > and please report any problems you encounter. | > and please report any problems you encounter. | ||||||
| 
 | 
 | ||||||
| Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead | Hashed Storage is the new storage behavior we are rolling out with 10.0. Instead | ||||||
|  |  | ||||||
|  | @ -48,9 +48,11 @@ GitLab removes events older than 1 year from the events table for performance re | ||||||
| 
 | 
 | ||||||
| ## List currently authenticated user's events | ## List currently authenticated user's events | ||||||
| 
 | 
 | ||||||
| >**Note:** This endpoint was introduced in GitLab 9.3. | >**Notes:** | ||||||
|  | > This endpoint was introduced in GitLab 9.3. | ||||||
|  | > `read_user` access was introduced in GitLab 11.3. | ||||||
| 
 | 
 | ||||||
| Get a list of events for the authenticated user. | Get a list of events for the authenticated user. Scope `read_user` or `api` is required. | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| GET /events | GET /events | ||||||
|  | @ -119,9 +121,11 @@ Example response: | ||||||
| 
 | 
 | ||||||
| ### Get user contribution events | ### Get user contribution events | ||||||
| 
 | 
 | ||||||
| >**Note:** Documentation was formerly located in the [Users API pages][users-api]. | >**Notes:** | ||||||
|  | > Documentation was formerly located in the [Users API pages][users-api]. | ||||||
|  | > `read_user` access was introduced in GitLab 11.3. | ||||||
| 
 | 
 | ||||||
| Get the contribution events for the specified user, sorted from newest to oldest. | Get the contribution events for the specified user, sorted from newest to oldest. Scope `read_user` or `api` is required. | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| GET /users/:id/events | GET /users/:id/events | ||||||
|  |  | ||||||
|  | @ -401,48 +401,6 @@ Get Flowdock service settings for a project. | ||||||
| GET /projects/:id/services/flowdock | GET /projects/:id/services/flowdock | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Gemnasium |  | ||||||
| 
 |  | ||||||
| Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities. |  | ||||||
| 
 |  | ||||||
| CAUTION: **Warning:** |  | ||||||
| Gemnasium service integration has been deprecated in GitLab 11.0. Gemnasium has been |  | ||||||
| [acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html) |  | ||||||
| in January 2018 and since May 15, 2018, the service provided by Gemnasium is no longer available. |  | ||||||
| You can [migrate from Gemnasium to GitLab](https://docs.gitlab.com/ee/user/project/import/gemnasium.html) |  | ||||||
| to keep monitoring your dependencies. |  | ||||||
| 
 |  | ||||||
| ### Create/Edit Gemnasium service |  | ||||||
| 
 |  | ||||||
| Set Gemnasium service for a project. |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| PUT /projects/:id/services/gemnasium |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Parameters: |  | ||||||
| 
 |  | ||||||
| | Parameter | Type | Required | Description | |  | ||||||
| | --------- | ---- | -------- | ----------- | |  | ||||||
| | `api_key` | string | true | Your personal API KEY on gemnasium.com | |  | ||||||
| | `token`  | string | true | The project's slug on gemnasium.com | |  | ||||||
| 
 |  | ||||||
| ### Delete Gemnasium service |  | ||||||
| 
 |  | ||||||
| Delete Gemnasium service for a project. |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| DELETE /projects/:id/services/gemnasium |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ### Get Gemnasium service settings |  | ||||||
| 
 |  | ||||||
| Get Gemnasium service settings for a project. |  | ||||||
| 
 |  | ||||||
| ``` |  | ||||||
| GET /projects/:id/services/gemnasium |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| ## Hangouts Chat | ## Hangouts Chat | ||||||
| 
 | 
 | ||||||
| Google GSuite team collaboration tool. | Google GSuite team collaboration tool. | ||||||
|  |  | ||||||
|  | @ -76,6 +76,8 @@ learn how to leverage its potential even more. | ||||||
| - [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md) | - [Trigger pipelines on a schedule](../user/project/pipelines/schedules.md) | ||||||
| - [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or | - [Kubernetes clusters](../user/project/clusters/index.md) - Integrate one or | ||||||
|   more Kubernetes clusters to your project |   more Kubernetes clusters to your project | ||||||
|  | - [Interactive web terminal](interactive_web_terminal/index.md) - Open an interactive | ||||||
|  |   web terminal to debug the running jobs | ||||||
| 
 | 
 | ||||||
| ## GitLab CI/CD for Docker | ## GitLab CI/CD for Docker | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 35 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 23 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 54 KiB | 
|  | @ -0,0 +1,52 @@ | ||||||
|  | # Getting started with interactive web terminals | ||||||
|  | 
 | ||||||
|  | > Introduced in GitLab 11.3. | ||||||
|  | 
 | ||||||
|  | CAUTION: **Warning:** | ||||||
|  | Interactive web terminals are in beta, so they might not work properly and | ||||||
|  | lack features. For more information [follow issue #25990](https://gitlab.com/gitlab-org/gitlab-ce/issues/25990). | ||||||
|  | 
 | ||||||
|  | Interactive web terminals give the user access to a terminal in GitLab for | ||||||
|  | running one-of commands for their CI pipeline. | ||||||
|  | 
 | ||||||
|  | NOTE: **Note:** | ||||||
|  | This is not available for the shared Runners on GitLab.com. | ||||||
|  | To make use of this feature, you need to provide your | ||||||
|  | [own Runner](https://docs.gitlab.com/runner/install/) and properly | ||||||
|  | [configure it](#configuration). | ||||||
|  | 
 | ||||||
|  | ## Configuration | ||||||
|  | 
 | ||||||
|  | Two things need to be configured for the interactive web terminal to work: | ||||||
|  | 
 | ||||||
|  | - The Runner needs to have [`[session_server]` configured | ||||||
|  |   properly][session-server] | ||||||
|  | - Web terminals need to be | ||||||
|  |   [enabled](../../administration/integration/terminal.md#enabling-and-disabling-terminal-support) | ||||||
|  | 
 | ||||||
|  | ## Debugging a running job | ||||||
|  | 
 | ||||||
|  | NOTE: **Note:** Not all executors are | ||||||
|  | [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart). | ||||||
|  | 
 | ||||||
|  | Sometimes, when a job is running, things don't go as you would expect, and it | ||||||
|  | would be helpful if one can have a shell to aid debugging. When a job is | ||||||
|  | running, on the right panel you can see a button `debug` that will open the terminal | ||||||
|  | for the current job. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | When clicked, a new tab will open to the terminal page where you can access | ||||||
|  | the terminal and type commands like a normal shell. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | If you have the terminal open and the job has finished with its tasks, the | ||||||
|  | terminal will block the job from finishing for the duration configured in | ||||||
|  | [`[session_server].terminal_max_retention_time`][session-server] until you | ||||||
|  | close the terminal window. | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | [session-server]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-session_server-section | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | # Smoke Tests | ||||||
|  | 
 | ||||||
|  | It is imperative in any testing suite that we have Smoke Tests.  In short, smoke tests are will run quick sanity | ||||||
|  | end-to-end functional tests from GitLab QA and are designed to run against the specified environment to ensure that  | ||||||
|  | basic functionality is working. | ||||||
|  | 
 | ||||||
|  | Currently, our suite consists of this basic functionality coverage: | ||||||
|  | 
 | ||||||
|  | - User Login (Standard Auth) | ||||||
|  | - Project Creation | ||||||
|  | - Issue Creation | ||||||
|  | - Merge Request Creation | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | [Return to Testing documentation](index.md) | ||||||
|  | @ -120,6 +120,14 @@ running feature tests (i.e. using Capybara) against it. | ||||||
| The actual test scenarios and steps are [part of GitLab Rails] so that they're | The actual test scenarios and steps are [part of GitLab Rails] so that they're | ||||||
| always in-sync with the codebase. | always in-sync with the codebase. | ||||||
| 
 | 
 | ||||||
|  | ### Smoke tests | ||||||
|  | 
 | ||||||
|  | Smoke tests are quick tests that may be run at any time (especially after the pre-deployment migrations). | ||||||
|  | 
 | ||||||
|  | Much like feature tests - these tests run against the UI and ensure that basic functionality is working. | ||||||
|  | 
 | ||||||
|  | > See [Smoke Tests](smoke.md) for more information. | ||||||
|  | 
 | ||||||
| Read a separate document about [end-to-end tests](end_to_end_tests.md) to | Read a separate document about [end-to-end tests](end_to_end_tests.md) to | ||||||
| learn more. | learn more. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -120,7 +120,7 @@ gitlabConfigStorageSize: 1Gi | ||||||
| Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego). | Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego). | ||||||
| 
 | 
 | ||||||
| > **Note:** | > **Note:** | ||||||
| Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [nip.io](http://nip.io) and [nip.io](http://nip.io) are unlikely to work. | Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [nip.io](http://nip.io) are unlikely to work. | ||||||
| 
 | 
 | ||||||
| ## Installing GitLab using the Helm Chart | ## Installing GitLab using the Helm Chart | ||||||
| > **Note:** | > **Note:** | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ Bitbucket.org account | ||||||
| 
 | 
 | ||||||
| ## Project services | ## Project services | ||||||
| 
 | 
 | ||||||
| Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, | Integration with services such as Campfire, Flowdock, HipChat, | ||||||
| Pivotal Tracker, and Slack are available in the form of a [Project Service][]. | Pivotal Tracker, and Slack are available in the form of a [Project Service][]. | ||||||
| 
 | 
 | ||||||
| [Project Service]: ../user/project/integrations/project_services.md | [Project Service]: ../user/project/integrations/project_services.md | ||||||
|  |  | ||||||
|  | @ -34,7 +34,6 @@ Click on the service links to see further configuration instructions and details | ||||||
| | [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | | [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | ||||||
| | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | ||||||
| | Flowdock | Flowdock is a collaboration web app for technical teams | | | Flowdock | Flowdock is a collaboration web app for technical teams | | ||||||
| | Gemnasium   _(Has been deprecated in GitLab 11.0)_ | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | |  | ||||||
| | [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat | | | [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat | | ||||||
| | [HipChat](hipchat.md) | Private group chat and IM | | | [HipChat](hipchat.md) | Private group chat and IM | | ||||||
| | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | ||||||
|  |  | ||||||
|  | @ -1102,6 +1102,7 @@ X-Gitlab-Event: Build Hook | ||||||
|   "build_finished_at": null, |   "build_finished_at": null, | ||||||
|   "build_duration": null, |   "build_duration": null, | ||||||
|   "build_allow_failure": false, |   "build_allow_failure": false, | ||||||
|  |   "build_failure_reason": "script_failure", | ||||||
|   "project_id": 380, |   "project_id": 380, | ||||||
|   "project_name": "gitlab-org/gitlab-test", |   "project_name": "gitlab-org/gitlab-test", | ||||||
|   "user": { |   "user": { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| module API | module API | ||||||
|   class Events < Grape::API |   class Events < Grape::API | ||||||
|     include PaginationParams |     include PaginationParams | ||||||
|  |     include APIGuard | ||||||
| 
 | 
 | ||||||
|     helpers do |     helpers do | ||||||
|       params :event_filter_params do |       params :event_filter_params do | ||||||
|  | @ -24,6 +25,8 @@ module API | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     resource :events do |     resource :events do | ||||||
|  |       allow_access_with_scope :read_user, if: -> (request) { request.get? } | ||||||
|  | 
 | ||||||
|       desc "List currently authenticated user's events" do |       desc "List currently authenticated user's events" do | ||||||
|         detail 'This feature was introduced in GitLab 9.3.' |         detail 'This feature was introduced in GitLab 9.3.' | ||||||
|         success Entities::Event |         success Entities::Event | ||||||
|  | @ -46,6 +49,8 @@ module API | ||||||
|       requires :id, type: String, desc: 'The ID or Username of the user' |       requires :id, type: String, desc: 'The ID or Username of the user' | ||||||
|     end |     end | ||||||
|     resource :users do |     resource :users do | ||||||
|  |       allow_access_with_scope :read_user, if: -> (request) { request.get? } | ||||||
|  | 
 | ||||||
|       desc 'Get the contribution events of a specified user' do |       desc 'Get the contribution events of a specified user' do | ||||||
|         detail 'This feature was introduced in GitLab 8.13.' |         detail 'This feature was introduced in GitLab 8.13.' | ||||||
|         success Entities::Event |         success Entities::Event | ||||||
|  |  | ||||||
|  | @ -354,20 +354,6 @@ module API | ||||||
|           desc: 'Flowdock token' |           desc: 'Flowdock token' | ||||||
|         } |         } | ||||||
|       ], |       ], | ||||||
|       'gemnasium' => [ |  | ||||||
|         { |  | ||||||
|           required: true, |  | ||||||
|           name: :api_key, |  | ||||||
|           type: String, |  | ||||||
|           desc: 'Your personal API key on gemnasium.com' |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           required: true, |  | ||||||
|           name: :token, |  | ||||||
|           type: String, |  | ||||||
|           desc: "The project's slug on gemnasium.com" |  | ||||||
|         } |  | ||||||
|       ], |  | ||||||
|       'hangouts-chat' => [ |       'hangouts-chat' => [ | ||||||
|         { |         { | ||||||
|           required: true, |           required: true, | ||||||
|  | @ -695,7 +681,6 @@ module API | ||||||
|       EmailsOnPushService, |       EmailsOnPushService, | ||||||
|       ExternalWikiService, |       ExternalWikiService, | ||||||
|       FlowdockService, |       FlowdockService, | ||||||
|       GemnasiumService, |  | ||||||
|       HangoutsChatService, |       HangoutsChatService, | ||||||
|       HipchatService, |       HipchatService, | ||||||
|       IrkerService, |       IrkerService, | ||||||
|  |  | ||||||
|  | @ -0,0 +1,77 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'uri' | ||||||
|  | 
 | ||||||
|  | module Banzai | ||||||
|  |   module Filter | ||||||
|  |     # HTML Filter for markdown links with spaces in the URLs | ||||||
|  |     # | ||||||
|  |     # Based on Banzai::Filter::AutolinkFilter | ||||||
|  |     # | ||||||
|  |     # CommonMark does not allow spaces in the url portion of a link. | ||||||
|  |     # For example, `[example](page slug)` is not valid.  However, | ||||||
|  |     # in our wikis, we support (via RedCarpet) this type of link, allowing | ||||||
|  |     # wiki pages to be easily linked by their title.  This filter adds that functionality. | ||||||
|  |     # The intent is for this to only be used in Wikis - in general, we want | ||||||
|  |     # to adhere to CommonMark's spec. | ||||||
|  |     # | ||||||
|  |     class SpacedLinkFilter < HTML::Pipeline::Filter | ||||||
|  |       include ActionView::Helpers::TagHelper | ||||||
|  | 
 | ||||||
|  |       # Pattern to match a standard markdown link | ||||||
|  |       # | ||||||
|  |       # Rubular: http://rubular.com/r/z9EAHxYmKI | ||||||
|  |       LINK_PATTERN = /\[([^\]]+)\]\(([^)"]+)(?: \"([^\"]+)\")?\)/ | ||||||
|  | 
 | ||||||
|  |       # Text matching LINK_PATTERN inside these elements will not be linked | ||||||
|  |       IGNORE_PARENTS = %w(a code kbd pre script style).to_set | ||||||
|  | 
 | ||||||
|  |       # The XPath query to use for finding text nodes to parse. | ||||||
|  |       TEXT_QUERY = %Q(descendant-or-self::text()[ | ||||||
|  |         not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')}) | ||||||
|  |         and contains(., ']\(') | ||||||
|  |       ]).freeze | ||||||
|  | 
 | ||||||
|  |       def call | ||||||
|  |         return doc if context[:markdown_engine] == :redcarpet | ||||||
|  | 
 | ||||||
|  |         doc.xpath(TEXT_QUERY).each do |node| | ||||||
|  |           content = node.to_html | ||||||
|  | 
 | ||||||
|  |           next unless content.match(LINK_PATTERN) | ||||||
|  | 
 | ||||||
|  |           html = spaced_link_filter(content) | ||||||
|  | 
 | ||||||
|  |           next if html == content | ||||||
|  | 
 | ||||||
|  |           node.replace(html) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         doc | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       private | ||||||
|  | 
 | ||||||
|  |       def spaced_link_match(link) | ||||||
|  |         match = LINK_PATTERN.match(link) | ||||||
|  |         return link unless match && match[1] && match[2] | ||||||
|  | 
 | ||||||
|  |         # escape the spaces in the url so that it's a valid markdown link, | ||||||
|  |         # then run it through the markdown processor again, let it do its magic | ||||||
|  |         text     = match[1] | ||||||
|  |         new_link = match[2].gsub(' ', '%20') | ||||||
|  |         title    = match[3] ? " \"#{match[3]}\"" : '' | ||||||
|  |         html     = Banzai::Filter::MarkdownFilter.call("[#{text}](#{new_link}#{title})", context) | ||||||
|  | 
 | ||||||
|  |         # link is wrapped in a <p>, so strip that off | ||||||
|  |         html.sub('<p>', '').chomp('</p>') | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def spaced_link_filter(text) | ||||||
|  |         Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:| | ||||||
|  |           spaced_link_match(link) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -5,6 +5,7 @@ module Banzai | ||||||
|         @filters ||= begin |         @filters ||= begin | ||||||
|           super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter) |           super.insert_after(Filter::TableOfContentsFilter, Filter::GollumTagsFilter) | ||||||
|                .insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter) |                .insert_before(Filter::TaskListFilter, Filter::WikiLinkFilter) | ||||||
|  |                .insert_before(Filter::WikiLinkFilter, Filter::SpacedLinkFilter) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ module Gitlab | ||||||
|           build_finished_at: build.finished_at, |           build_finished_at: build.finished_at, | ||||||
|           build_duration: build.duration, |           build_duration: build.duration, | ||||||
|           build_allow_failure: build.allow_failure, |           build_allow_failure: build.allow_failure, | ||||||
|  |           build_failure_reason: build.failure_reason, | ||||||
| 
 | 
 | ||||||
|           # TODO: do we still need it? |           # TODO: do we still need it? | ||||||
|           project_id: project.id, |           project_id: project.id, | ||||||
|  |  | ||||||
|  | @ -226,6 +226,7 @@ module Gitlab | ||||||
|         @new_file = diff.from_id == BLANK_SHA |         @new_file = diff.from_id == BLANK_SHA | ||||||
|         @renamed_file = diff.from_path != diff.to_path |         @renamed_file = diff.from_path != diff.to_path | ||||||
|         @deleted_file = diff.to_id == BLANK_SHA |         @deleted_file = diff.to_id == BLANK_SHA | ||||||
|  |         @too_large = diff.too_large if diff.respond_to?(:too_large) | ||||||
| 
 | 
 | ||||||
|         collapse! if diff.respond_to?(:collapsed) && diff.collapsed |         collapse! if diff.respond_to?(:collapsed) && diff.collapsed | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| module Gitlab | module Gitlab | ||||||
|   module GitalyClient |   module GitalyClient | ||||||
|     class Diff |     class Diff | ||||||
|       ATTRS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed).freeze |       ATTRS = %i(from_path to_path old_mode new_mode from_id to_id patch overflow_marker collapsed too_large).freeze | ||||||
| 
 | 
 | ||||||
|       include AttributesBag |       include AttributesBag | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ module Gitlab | ||||||
|           @note = note |           @note = note | ||||||
|           @project = project |           @project = project | ||||||
|           @client = client |           @client = client | ||||||
|           @user_finder = UserFinder.new(project, client) |           @user_finder = GithubImport::UserFinder.new(project, client) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def execute |         def execute | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ module Gitlab | ||||||
|           @issue = issue |           @issue = issue | ||||||
|           @project = project |           @project = project | ||||||
|           @client = client |           @client = client | ||||||
|           @user_finder = UserFinder.new(project, client) |           @user_finder = GithubImport::UserFinder.new(project, client) | ||||||
|           @milestone_finder = MilestoneFinder.new(project) |           @milestone_finder = MilestoneFinder.new(project) | ||||||
|           @issuable_finder = GithubImport::IssuableFinder.new(project, issue) |           @issuable_finder = GithubImport::IssuableFinder.new(project, issue) | ||||||
|         end |         end | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ module Gitlab | ||||||
|           @note = note |           @note = note | ||||||
|           @project = project |           @project = project | ||||||
|           @client = client |           @client = client | ||||||
|           @user_finder = UserFinder.new(project, client) |           @user_finder = GithubImport::UserFinder.new(project, client) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def execute |         def execute | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ module Gitlab | ||||||
|           @pull_request = pull_request |           @pull_request = pull_request | ||||||
|           @project = project |           @project = project | ||||||
|           @client = client |           @client = client | ||||||
|           @user_finder = UserFinder.new(project, client) |           @user_finder = GithubImport::UserFinder.new(project, client) | ||||||
|           @milestone_finder = MilestoneFinder.new(project) |           @milestone_finder = MilestoneFinder.new(project) | ||||||
|           @issuable_finder = |           @issuable_finder = | ||||||
|             GithubImport::IssuableFinder.new(project, pull_request) |             GithubImport::IssuableFinder.new(project, pull_request) | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ module Gitlab | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def safe_keys |       def safe_keys | ||||||
|         issuable_builder::SAFE_HOOK_ATTRIBUTES + issuable_builder::SAFE_HOOK_RELATIONS |         issuable_builder.safe_hook_attributes + issuable_builder::SAFE_HOOK_RELATIONS | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       private |       private | ||||||
|  |  | ||||||
|  | @ -1,50 +1,52 @@ | ||||||
| module Gitlab | module Gitlab | ||||||
|   module HookData |   module HookData | ||||||
|     class IssueBuilder < BaseBuilder |     class IssueBuilder < BaseBuilder | ||||||
|       SAFE_HOOK_ATTRIBUTES = %i[ |  | ||||||
|         assignee_id |  | ||||||
|         author_id |  | ||||||
|         closed_at |  | ||||||
|         confidential |  | ||||||
|         created_at |  | ||||||
|         description |  | ||||||
|         due_date |  | ||||||
|         id |  | ||||||
|         iid |  | ||||||
|         last_edited_at |  | ||||||
|         last_edited_by_id |  | ||||||
|         milestone_id |  | ||||||
|         moved_to_id |  | ||||||
|         project_id |  | ||||||
|         relative_position |  | ||||||
|         state |  | ||||||
|         time_estimate |  | ||||||
|         title |  | ||||||
|         updated_at |  | ||||||
|         updated_by_id |  | ||||||
|       ].freeze |  | ||||||
| 
 |  | ||||||
|       SAFE_HOOK_RELATIONS = %i[ |       SAFE_HOOK_RELATIONS = %i[ | ||||||
|         assignees |         assignees | ||||||
|         labels |         labels | ||||||
|         total_time_spent |         total_time_spent | ||||||
|       ].freeze |       ].freeze | ||||||
| 
 | 
 | ||||||
|  |       def self.safe_hook_attributes | ||||||
|  |         %i[ | ||||||
|  |           assignee_id | ||||||
|  |           author_id | ||||||
|  |           closed_at | ||||||
|  |           confidential | ||||||
|  |           created_at | ||||||
|  |           description | ||||||
|  |           due_date | ||||||
|  |           id | ||||||
|  |           iid | ||||||
|  |           last_edited_at | ||||||
|  |           last_edited_by_id | ||||||
|  |           milestone_id | ||||||
|  |           moved_to_id | ||||||
|  |           project_id | ||||||
|  |           relative_position | ||||||
|  |           state | ||||||
|  |           time_estimate | ||||||
|  |           title | ||||||
|  |           updated_at | ||||||
|  |           updated_by_id | ||||||
|  |         ].freeze | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       alias_method :issue, :object |       alias_method :issue, :object | ||||||
| 
 | 
 | ||||||
|       def build |       def build | ||||||
|         attrs = { |         attrs = { | ||||||
|           description: absolute_image_urls(issue.description), |             description: absolute_image_urls(issue.description), | ||||||
|           url: Gitlab::UrlBuilder.build(issue), |             url: Gitlab::UrlBuilder.build(issue), | ||||||
|           total_time_spent: issue.total_time_spent, |             total_time_spent: issue.total_time_spent, | ||||||
|           human_total_time_spent: issue.human_total_time_spent, |             human_total_time_spent: issue.human_total_time_spent, | ||||||
|           human_time_estimate: issue.human_time_estimate, |             human_time_estimate: issue.human_time_estimate, | ||||||
|           assignee_ids: issue.assignee_ids, |             assignee_ids: issue.assignee_ids, | ||||||
|           assignee_id: issue.assignee_ids.first # This key is deprecated |             assignee_id: issue.assignee_ids.first # This key is deprecated | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         issue.attributes.with_indifferent_access.slice(*SAFE_HOOK_ATTRIBUTES) |         issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes) | ||||||
|           .merge!(attrs) |             .merge!(attrs) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,33 +1,35 @@ | ||||||
| module Gitlab | module Gitlab | ||||||
|   module HookData |   module HookData | ||||||
|     class MergeRequestBuilder < BaseBuilder |     class MergeRequestBuilder < BaseBuilder | ||||||
|       SAFE_HOOK_ATTRIBUTES = %i[ |       def self.safe_hook_attributes | ||||||
|         assignee_id |         %i[ | ||||||
|         author_id |           assignee_id | ||||||
|         created_at |           author_id | ||||||
|         description |           created_at | ||||||
|         head_pipeline_id |           description | ||||||
|         id |           head_pipeline_id | ||||||
|         iid |           id | ||||||
|         last_edited_at |           iid | ||||||
|         last_edited_by_id |           last_edited_at | ||||||
|         merge_commit_sha |           last_edited_by_id | ||||||
|         merge_error |           merge_commit_sha | ||||||
|         merge_params |           merge_error | ||||||
|         merge_status |           merge_params | ||||||
|         merge_user_id |           merge_status | ||||||
|         merge_when_pipeline_succeeds |           merge_user_id | ||||||
|         milestone_id |           merge_when_pipeline_succeeds | ||||||
|         source_branch |           milestone_id | ||||||
|         source_project_id |           source_branch | ||||||
|         state |           source_project_id | ||||||
|         target_branch |           state | ||||||
|         target_project_id |           target_branch | ||||||
|         time_estimate |           target_project_id | ||||||
|         title |           time_estimate | ||||||
|         updated_at |           title | ||||||
|         updated_by_id |           updated_at | ||||||
|       ].freeze |           updated_by_id | ||||||
|  |         ].freeze | ||||||
|  |       end | ||||||
| 
 | 
 | ||||||
|       SAFE_HOOK_RELATIONS = %i[ |       SAFE_HOOK_RELATIONS = %i[ | ||||||
|         assignee |         assignee | ||||||
|  | @ -50,8 +52,8 @@ module Gitlab | ||||||
|           human_time_estimate: merge_request.human_time_estimate |           human_time_estimate: merge_request.human_time_estimate | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         merge_request.attributes.with_indifferent_access.slice(*SAFE_HOOK_ATTRIBUTES) |         merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes) | ||||||
|           .merge!(attrs) |             .merge!(attrs) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								qa/README.md
								
								
								
								
							
							
						
						
									
										12
									
								
								qa/README.md
								
								
								
								
							|  | @ -35,7 +35,7 @@ following call would login to a local [GDK] instance and run all specs in | ||||||
| `qa/specs/features`: | `qa/specs/features`: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| bin/qa Test::Instance http://localhost:3000 | bin/qa Test::Instance::All http://localhost:3000 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Writing tests | ### Writing tests | ||||||
|  | @ -48,14 +48,14 @@ You can also supply specific tests to run as another parameter. For example, to | ||||||
| run the repository-related specs, you can execute: | run the repository-related specs, you can execute: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| bin/qa Test::Instance http://localhost qa/specs/features/repository/ | bin/qa Test::Instance::All http://localhost qa/specs/features/repository/ | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| Since the arguments would be passed to `rspec`, you could use all `rspec` | Since the arguments would be passed to `rspec`, you could use all `rspec` | ||||||
| options there. For example, passing `--backtrace` and also line number: | options there. For example, passing `--backtrace` and also line number: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| bin/qa Test::Instance http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace | bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Overriding the authenticated user | ### Overriding the authenticated user | ||||||
|  | @ -67,7 +67,7 @@ If you need to authenticate as a different user, you can provide the | ||||||
| `GITLAB_USERNAME` and `GITLAB_PASSWORD` environment variables: | `GITLAB_USERNAME` and `GITLAB_PASSWORD` environment variables: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance https://gitlab.example.com | GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password bin/qa Test::Instance::All https://gitlab.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| If your user doesn't have permission to default sandbox group | If your user doesn't have permission to default sandbox group | ||||||
|  | @ -75,13 +75,13 @@ If your user doesn't have permission to default sandbox group | ||||||
| `GITLAB_SANDBOX_NAME`: | `GITLAB_SANDBOX_NAME`: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com | GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user: | In addition, the `GITLAB_USER_TYPE` can be set to "ldap" to sign in as an LDAP user: | ||||||
| 
 | 
 | ||||||
| ``` | ``` | ||||||
| GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance https://gitlab.example.com | GITLAB_USER_TYPE=ldap GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sandbox bin/qa Test::Instance::All https://gitlab.example.com | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables). | All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa#supported-environment-variables). | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								qa/qa.rb
								
								
								
								
							
							
						
						
									
										6
									
								
								qa/qa.rb
								
								
								
								
							|  | @ -77,14 +77,16 @@ module QA | ||||||
|     # |     # | ||||||
|     autoload :Bootable, 'qa/scenario/bootable' |     autoload :Bootable, 'qa/scenario/bootable' | ||||||
|     autoload :Actable, 'qa/scenario/actable' |     autoload :Actable, 'qa/scenario/actable' | ||||||
|     autoload :Taggable, 'qa/scenario/taggable' |  | ||||||
|     autoload :Template, 'qa/scenario/template' |     autoload :Template, 'qa/scenario/template' | ||||||
| 
 | 
 | ||||||
|     ## |     ## | ||||||
|     # Test scenario entrypoints. |     # Test scenario entrypoints. | ||||||
|     # |     # | ||||||
|     module Test |     module Test | ||||||
|       autoload :Instance, 'qa/scenario/test/instance' |       module Instance | ||||||
|  |         autoload :All, 'qa/scenario/test/instance/all' | ||||||
|  |         autoload :Smoke, 'qa/scenario/test/instance/smoke' | ||||||
|  |       end | ||||||
| 
 | 
 | ||||||
|       module Integration |       module Integration | ||||||
|         autoload :Github, 'qa/scenario/test/integration/github' |         autoload :Github, 'qa/scenario/test/integration/github' | ||||||
|  |  | ||||||
|  | @ -4,7 +4,12 @@ module QA | ||||||
|       class Fork < Factory::Base |       class Fork < Factory::Base | ||||||
|         dependency Factory::Repository::ProjectPush, as: :push |         dependency Factory::Repository::ProjectPush, as: :push | ||||||
| 
 | 
 | ||||||
|         dependency Factory::Resource::User, as: :user |         dependency Factory::Resource::User, as: :user do |user| | ||||||
|  |           if Runtime::Env.forker? | ||||||
|  |             user.username = Runtime::Env.forker_username | ||||||
|  |             user.password = Runtime::Env.forker_password | ||||||
|  |           end | ||||||
|  |         end | ||||||
| 
 | 
 | ||||||
|         product(:user) { |factory| factory.user } |         product(:user) { |factory| factory.user } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,28 +4,52 @@ module QA | ||||||
|   module Factory |   module Factory | ||||||
|     module Resource |     module Resource | ||||||
|       class User < Factory::Base |       class User < Factory::Base | ||||||
|         attr_accessor :name, :username, :email, :password |         attr_reader :unique_id | ||||||
|  |         attr_writer :username, :password, :name, :email | ||||||
| 
 | 
 | ||||||
|         def initialize |         def initialize | ||||||
|           @name = "name-#{SecureRandom.hex(8)}" |           @unique_id = SecureRandom.hex(8) | ||||||
|           @username = "username-#{SecureRandom.hex(8)}" |         end | ||||||
|           @email = "mail#{SecureRandom.hex(8)}@mail.com" | 
 | ||||||
|           @password = 'password' |         def username | ||||||
|  |           @username ||= "qa-user-#{unique_id}" | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def password | ||||||
|  |           @password ||= 'password' | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def name | ||||||
|  |           @name ||= username | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def email | ||||||
|  |           @email ||= "#{username}@example.com" | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def credentials_given? | ||||||
|  |           defined?(@username) && defined?(@password) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         product(:name) { |factory| factory.name } |         product(:name) { |factory| factory.name } | ||||||
| 
 |  | ||||||
|         product(:username) { |factory| factory.username } |         product(:username) { |factory| factory.username } | ||||||
| 
 |  | ||||||
|         product(:email) { |factory| factory.email } |         product(:email) { |factory| factory.email } | ||||||
| 
 |  | ||||||
|         product(:password) { |factory| factory.password } |         product(:password) { |factory| factory.password } | ||||||
| 
 | 
 | ||||||
|         def fabricate! |         def fabricate! | ||||||
|           Page::Menu::Main.act { sign_out } |           Page::Menu::Main.perform { |main| main.sign_out } | ||||||
|           Page::Main::Login.act { switch_to_register_tab } | 
 | ||||||
|           Page::Main::SignUp.perform do |page| |           if credentials_given? | ||||||
|             page.sign_up!(name: name, username: username, email: email, password: password) |             Page::Main::Login.perform do |login| | ||||||
|  |               login.sign_in_using_credentials(self) | ||||||
|  |             end | ||||||
|  |           else | ||||||
|  |             Page::Main::Login.perform do |login| | ||||||
|  |               login.switch_to_register_tab | ||||||
|  |             end | ||||||
|  |             Page::Main::SignUp.perform do |signup| | ||||||
|  |               signup.sign_up!(self) | ||||||
|  |             end | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def use_default_credentials |       def use_default_credentials | ||||||
|         self.username = Runtime::User.name |         self.username = Runtime::User.username | ||||||
|         self.password = Runtime::User.password |         self.password = Runtime::User.password | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,17 +40,19 @@ module QA | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def sign_in_using_credentials |         def sign_in_using_credentials(user = nil) | ||||||
|           # Don't try to log-in if we're already logged-in |           # Don't try to log-in if we're already logged-in | ||||||
|           return if Page::Menu::Main.act { has_personal_area?(wait: 0) } |           return if Page::Menu::Main.act { has_personal_area?(wait: 0) } | ||||||
| 
 | 
 | ||||||
|           using_wait_time 0 do |           using_wait_time 0 do | ||||||
|             set_initial_password_if_present |             set_initial_password_if_present | ||||||
| 
 | 
 | ||||||
|  |             raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given? | ||||||
|  | 
 | ||||||
|             if Runtime::User.ldap_user? |             if Runtime::User.ldap_user? | ||||||
|               sign_in_using_ldap_credentials |               sign_in_using_ldap_credentials | ||||||
|             else |             else | ||||||
|               sign_in_using_gitlab_credentials |               sign_in_using_gitlab_credentials(user || Runtime::User) | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|  | @ -69,21 +71,30 @@ module QA | ||||||
|           click_on 'Register' |           click_on 'Register' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  |         def switch_to_ldap_tab | ||||||
|  |           click_on 'LDAP' | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def switch_to_standard_tab | ||||||
|  |           click_on 'Standard' | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|         private |         private | ||||||
| 
 | 
 | ||||||
|         def sign_in_using_ldap_credentials |         def sign_in_using_ldap_credentials | ||||||
|           click_link 'LDAP' |           switch_to_ldap_tab | ||||||
| 
 | 
 | ||||||
|           fill_in :username, with: Runtime::User.ldap_username |           fill_in :username, with: Runtime::User.ldap_username | ||||||
|           fill_in :password, with: Runtime::User.ldap_password |           fill_in :password, with: Runtime::User.ldap_password | ||||||
|           click_button 'Sign in' |           click_button 'Sign in' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def sign_in_using_gitlab_credentials |         def sign_in_using_gitlab_credentials(user) | ||||||
|           click_link 'Standard' if page.has_content?('LDAP') |           switch_to_sign_in_tab unless page.has_button?('Sign in') | ||||||
|  |           switch_to_standard_tab if page.has_content?('LDAP') | ||||||
| 
 | 
 | ||||||
|           fill_in :user_login, with: Runtime::User.name |           fill_in :user_login, with: user.username | ||||||
|           fill_in :user_password, with: Runtime::User.password |           fill_in :user_password, with: user.password | ||||||
|           click_button 'Sign in' |           click_button 'Sign in' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,12 +11,12 @@ module QA | ||||||
|           element :register_button, 'submit "Register"' |           element :register_button, 'submit "Register"' | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def sign_up!(name:, username:, email:, password:) |         def sign_up!(user) | ||||||
|           fill_in :new_user_name, with: name |           fill_in :new_user_name, with: user.name | ||||||
|           fill_in :new_user_username, with: username |           fill_in :new_user_username, with: user.username | ||||||
|           fill_in :new_user_email, with: email |           fill_in :new_user_email, with: user.email | ||||||
|           fill_in :new_user_email_confirmation, with: email |           fill_in :new_user_email_confirmation, with: user.email | ||||||
|           fill_in :new_user_password, with: password |           fill_in :new_user_password, with: user.password | ||||||
|           click_button 'Register' |           click_button 'Register' | ||||||
| 
 | 
 | ||||||
|           Page::Menu::Main.act { has_personal_area? } |           Page::Menu::Main.act { has_personal_area? } | ||||||
|  |  | ||||||
|  | @ -23,7 +23,13 @@ module QA | ||||||
|             # After we fill the key, JS would generate another field so |             # After we fill the key, JS would generate another field so | ||||||
|             # we need to use the same index to find the corresponding one. |             # we need to use the same index to find the corresponding one. | ||||||
|             keys[index].set(key) |             keys[index].set(key) | ||||||
|             all_elements(:ci_variable_input_value)[index].set(value) |             node = all_elements(:ci_variable_input_value)[index] | ||||||
|  | 
 | ||||||
|  |             # Simply run `node.set(value)` is too slow for long text here, | ||||||
|  |             # so we need to run JavaScript directly to set the value. | ||||||
|  |             # The code was inspired from: | ||||||
|  |             # https://github.com/teamcapybara/capybara/blob/679548cea10773d45e32808f4d964377cfe5e892/lib/capybara/selenium/node.rb#L217 | ||||||
|  |             execute_script("arguments[0].value = #{value.to_json}", node) | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           def save_variables |           def save_variables | ||||||
|  |  | ||||||
|  | @ -39,6 +39,18 @@ module QA | ||||||
|         ENV['GITLAB_PASSWORD'] |         ENV['GITLAB_PASSWORD'] | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       def forker? | ||||||
|  |         forker_username && forker_password | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def forker_username | ||||||
|  |         ENV['GITLAB_FORKER_USERNAME'] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       def forker_password | ||||||
|  |         ENV['GITLAB_FORKER_PASSWORD'] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       def ldap_username |       def ldap_username | ||||||
|         ENV['GITLAB_LDAP_USERNAME'] |         ENV['GITLAB_LDAP_USERNAME'] | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -3,12 +3,12 @@ module QA | ||||||
|     module User |     module User | ||||||
|       extend self |       extend self | ||||||
| 
 | 
 | ||||||
|       def default_name |       def default_username | ||||||
|         'root' |         'root' | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def name |       def username | ||||||
|         Runtime::Env.user_username || default_name |         Runtime::Env.user_username || default_username | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def password |       def password | ||||||
|  |  | ||||||
|  | @ -1,17 +0,0 @@ | ||||||
| module QA |  | ||||||
|   module Scenario |  | ||||||
|     module Taggable |  | ||||||
|       # rubocop:disable Gitlab/ModuleWithInstanceVariables |  | ||||||
| 
 |  | ||||||
|       def tags(*tags) |  | ||||||
|         @tags = tags |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       def focus |  | ||||||
|         @tags.to_a |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       # rubocop:enable Gitlab/ModuleWithInstanceVariables |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -1,15 +1,36 @@ | ||||||
| module QA | module QA | ||||||
|   module Scenario |   module Scenario | ||||||
|     class Template |     class Template | ||||||
|       def self.perform(*args) |       class << self | ||||||
|         new.tap do |scenario| |         def perform(*args) | ||||||
|           yield scenario if block_given? |           new.tap do |scenario| | ||||||
|           break scenario.perform(*args) |             yield scenario if block_given? | ||||||
|  |             break scenario.perform(*args) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def tags(*tags) | ||||||
|  |           @tags = tags | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         def focus | ||||||
|  |           @tags.to_a | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def perform(*_args) |       def perform(address, *rspec_options) | ||||||
|         raise NotImplementedError |         Runtime::Scenario.define(:gitlab_address, address) | ||||||
|  | 
 | ||||||
|  |         Specs::Runner.perform do |specs| | ||||||
|  |           specs.tty = true | ||||||
|  |           specs.tags = self.class.focus | ||||||
|  |           specs.options = | ||||||
|  |             if rspec_options.any? | ||||||
|  |               rspec_options | ||||||
|  |             else | ||||||
|  |               ::File.expand_path('../specs/features', __dir__) | ||||||
|  |             end | ||||||
|  |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| module QA |  | ||||||
|   module Scenario |  | ||||||
|     module Test |  | ||||||
|       ## |  | ||||||
|       # Base class for running the suite against any GitLab instance, |  | ||||||
|       # including staging and on-premises installation. |  | ||||||
|       # |  | ||||||
|       class Instance < Template |  | ||||||
|         include Bootable |  | ||||||
|         extend Taggable |  | ||||||
| 
 |  | ||||||
|         tags :core |  | ||||||
| 
 |  | ||||||
|         def perform(address, *rspec_options) |  | ||||||
|           Runtime::Scenario.define(:gitlab_address, address) |  | ||||||
| 
 |  | ||||||
|           ## |  | ||||||
|           # Perform before hooks, which are different for CE and EE |  | ||||||
|           # |  | ||||||
|           Runtime::Release.perform_before_hooks |  | ||||||
| 
 |  | ||||||
|           Specs::Runner.perform do |specs| |  | ||||||
|             specs.tty = true |  | ||||||
|             specs.tags = self.class.focus |  | ||||||
|             specs.options = |  | ||||||
|               if rspec_options.any? |  | ||||||
|                 rspec_options |  | ||||||
|               else |  | ||||||
|                 ::File.expand_path('../../specs/features', __dir__) |  | ||||||
|               end |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | module QA | ||||||
|  |   module Scenario | ||||||
|  |     module Test | ||||||
|  |       ## | ||||||
|  |       # Base class for running the suite against any GitLab instance, | ||||||
|  |       # including staging and on-premises installation. | ||||||
|  |       # | ||||||
|  |       module Instance | ||||||
|  |         class All < Template | ||||||
|  |           include Bootable | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | module QA | ||||||
|  |   module Scenario | ||||||
|  |     module Test | ||||||
|  |       module Instance | ||||||
|  |         ## | ||||||
|  |         # Base class for running the suite against any GitLab instance, | ||||||
|  |         # including staging and on-premises installation. | ||||||
|  |         # | ||||||
|  |         class Smoke < Template | ||||||
|  |           include Bootable | ||||||
|  | 
 | ||||||
|  |           tags :smoke | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| require 'securerandom' | require 'securerandom' | ||||||
| 
 | 
 | ||||||
| module QA | module QA | ||||||
|   describe 'API basics', :core do |   describe 'API basics' do | ||||||
|     before(:context) do |     before(:context) do | ||||||
|       @api_client = Runtime::API::Client.new(:gitlab) |       @api_client = Runtime::API::Client.new(:gitlab) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     let(:project_name) { "api-basics-#{SecureRandom.hex(8)}" } |     let(:project_name) { "api-basics-#{SecureRandom.hex(8)}" } | ||||||
|     let(:sanitized_project_path) { CGI.escape("#{Runtime::User.name}/#{project_name}") } |     let(:sanitized_project_path) { CGI.escape("#{Runtime::User.username}/#{project_name}") } | ||||||
| 
 | 
 | ||||||
|     it 'user creates a project with a file and deletes them afterwards' do |     it 'user creates a project with a file and deletes them afterwards' do | ||||||
|       create_project_request = Runtime::API::Request.new(@api_client, '/projects') |       create_project_request = Runtime::API::Request.new(@api_client, '/projects') | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'API users', :core do |   describe 'API users' do | ||||||
|     before(:context) do |     before(:context) do | ||||||
|       @api_client = Runtime::API::Client.new(:gitlab) |       @api_client = Runtime::API::Client.new(:gitlab) | ||||||
|     end |     end | ||||||
|  | @ -14,11 +14,11 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       it 'submit request with a valid user name' do |       it 'submit request with a valid user name' do | ||||||
|         get request.url, { params: { username: Runtime::User.name } } |         get request.url, { params: { username: Runtime::User.username } } | ||||||
| 
 | 
 | ||||||
|         expect_status(200) |         expect_status(200) | ||||||
|         expect(json_body).to contain_exactly( |         expect(json_body).to contain_exactly( | ||||||
|           a_hash_including(username: Runtime::User.name) |           a_hash_including(username: Runtime::User.username) | ||||||
|         ) |         ) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | module QA | ||||||
|  |   describe 'basic user login', :smoke do | ||||||
|  |     it 'user logs in using basic credentials' do | ||||||
|  |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|  |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  | 
 | ||||||
|  |       # TODO, since `Signed in successfully` message was removed | ||||||
|  |       # this is the only way to tell if user is signed in correctly. | ||||||
|  |       # | ||||||
|  |       Page::Menu::Main.perform do |menu| | ||||||
|  |         expect(menu).to have_personal_area | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'LDAP user login', :ldap do |   describe 'LDAP user login', :orchestrated, :ldap do | ||||||
|     before do |     before do | ||||||
|       Runtime::Env.user_type = 'ldap' |       Runtime::Env.user_type = 'ldap' | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'create a new group', :mattermost do |   describe 'create a new group', :orchestrated, :mattermost do | ||||||
|     it 'creating a group with a mattermost team' do |     it 'creating a group with a mattermost team' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'logging in to Mattermost', :mattermost do |   describe 'logging in to Mattermost', :orchestrated, :mattermost do | ||||||
|     it 'can use gitlab oauth' do |     it 'can use gitlab oauth' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) do |       Runtime::Browser.visit(:gitlab, Page::Main::Login) do | ||||||
|         Page::Main::Login.act { sign_in_using_credentials } |         Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'creates a merge request', :core do |   describe 'creates a merge request with milestone' do | ||||||
|     it 'user creates a new merge request'  do |     it 'user creates a new merge request'  do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  | @ -29,4 +29,25 @@ module QA | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   describe 'creates a merge request', :smoke do | ||||||
|  |     it 'user creates a new merge request'  do | ||||||
|  |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|  |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  | 
 | ||||||
|  |       current_project = Factory::Resource::Project.fabricate! do |project| | ||||||
|  |         project.name = 'project-with-merge-request' | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       Factory::Resource::MergeRequest.fabricate! do |merge_request| | ||||||
|  |         merge_request.title = 'This is a merge request' | ||||||
|  |         merge_request.description = 'Great feature' | ||||||
|  |         merge_request.project = current_project | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       expect(page).to have_content('This is a merge request') | ||||||
|  |       expect(page).to have_content('Great feature') | ||||||
|  |       expect(page).to have_content(/Opened [\w\s]+ ago/) | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'merge request rebase', :core do |   describe 'merge request rebase' do | ||||||
|     it 'rebases source branch of merge request'  do |     it 'rebases source branch of merge request'  do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'merge request squash commits', :core do |   describe 'merge request squash commits' do | ||||||
|     it 'when squash commits is marked before merge'  do |     it 'when squash commits is marked before merge'  do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'activity page', :core do |   describe 'activity page' do | ||||||
|     it 'push creates an event in the activity page' do |     it 'push creates an event in the activity page' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'deploy keys support', :core do |   describe 'deploy keys support' do | ||||||
|     it 'user adds a deploy key' do |     it 'user adds a deploy key' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'secret variables support', :core do |   describe 'secret variables support' do | ||||||
|     it 'user adds a secret variable' do |     it 'user adds a secret variable' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| require 'pathname' | require 'pathname' | ||||||
| 
 | 
 | ||||||
| module QA | module QA | ||||||
|   describe 'Auto Devops', :kubernetes do |   describe 'Auto Devops', :orchestrated, :kubernetes do | ||||||
|     after do |     after do | ||||||
|       @cluster&.remove! |       @cluster&.remove! | ||||||
|     end |     end | ||||||
|  | @ -15,6 +15,13 @@ module QA | ||||||
|         p.description = 'Project with Auto Devops' |         p.description = 'Project with Auto Devops' | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       # Disable code_quality check in Auto DevOps pipeline as it takes | ||||||
|  |       # too long and times out the test | ||||||
|  |       Factory::Resource::SecretVariable.fabricate! do |resource| | ||||||
|  |         resource.key = 'CODE_QUALITY_DISABLED' | ||||||
|  |         resource.value = '1' | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       # Create Auto Devops compatible repo |       # Create Auto Devops compatible repo | ||||||
|       Factory::Repository::ProjectPush.fabricate! do |push| |       Factory::Repository::ProjectPush.fabricate! do |push| | ||||||
|         push.project = project |         push.project = project | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'creates issue', :core do |   describe 'creates issue', :smoke do | ||||||
|     let(:issue_title) { 'issue title' } |     let(:issue_title) { 'issue title' } | ||||||
| 
 | 
 | ||||||
|     def create_issue |     def create_issue | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'create a new project', :core do |   describe 'create a new project', :smoke do | ||||||
|     it 'user creates a new project' do |     it 'user creates a new project' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| require 'digest/sha1' | require 'digest/sha1' | ||||||
| 
 | 
 | ||||||
| module QA | module QA | ||||||
|   describe 'cloning code using a deploy key', :core, :docker do |   describe 'cloning code using a deploy key', :docker do | ||||||
|     def login |     def login | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'Project fork', :core do |   describe 'Project fork' do | ||||||
|     it 'can submit merge requests to upstream master' do |     it 'can submit merge requests to upstream master' do | ||||||
|       Runtime::Browser.visit(:gitlab, Page::Main::Login) |       Runtime::Browser.visit(:gitlab, Page::Main::Login) | ||||||
|       Page::Main::Login.act { sign_in_using_credentials } |       Page::Main::Login.act { sign_in_using_credentials } | ||||||
|  | @ -8,14 +8,12 @@ module QA | ||||||
|         merge_request.fork_branch = 'feature-branch' |         merge_request.fork_branch = 'feature-branch' | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       Page::Menu::Main.act { sign_out } |       Page::Menu::Main.perform { |main| main.sign_out } | ||||||
|       Page::Main::Login.act do |       Page::Main::Login.perform { |login| login.sign_in_using_credentials } | ||||||
|         switch_to_sign_in_tab |  | ||||||
|         sign_in_using_credentials |  | ||||||
|       end |  | ||||||
| 
 | 
 | ||||||
|       merge_request.visit! |       merge_request.visit! | ||||||
|       Page::MergeRequest::Show.act { merge! } | 
 | ||||||
|  |       Page::MergeRequest::Show.perform { |show| show.merge! } | ||||||
| 
 | 
 | ||||||
|       expect(page).to have_content('The changes were merged') |       expect(page).to have_content('The changes were merged') | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'user imports a GitHub repo', :core, :github do |   describe 'user imports a GitHub repo', :orchestrated, :github do | ||||||
|     let(:imported_project) do |     let(:imported_project) do | ||||||
|       Factory::Resource::ProjectImportedFromGithub.fabricate! do |project| |       Factory::Resource::ProjectImportedFromGithub.fabricate! do |project| | ||||||
|         project.name = 'imported-project' |         project.name = 'imported-project' | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| module QA | module QA | ||||||
|   describe 'CI/CD Pipelines', :core, :docker do |   describe 'CI/CD Pipelines', :orchestrated, :docker do | ||||||
|     let(:executor) { "qa-runner-#{Time.now.to_i}" } |     let(:executor) { "qa-runner-#{Time.now.to_i}" } | ||||||
| 
 | 
 | ||||||
|     after do |     after do | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue