Commit 69b4b270 authored by Marin Jankovski's avatar Marin Jankovski

Merge branch '11-11-stable-prepare-rc3' into '11-11-stable'

Prepare 11.11.0-rc3 release

See merge request gitlab-org/gitlab-ce!28313
parents 4b358114 abac5047
...@@ -147,7 +147,7 @@ function deferredInitialisation() { ...@@ -147,7 +147,7 @@ function deferredInitialisation() {
const canaryBadge = document.querySelector('.js-canary-badge'); const canaryBadge = document.querySelector('.js-canary-badge');
const canaryLink = document.querySelector('.js-canary-link'); const canaryLink = document.querySelector('.js-canary-link');
if (canaryBadge) { if (canaryBadge) {
canaryBadge.classList.remove('hidden'); canaryBadge.classList.add('hidden');
} }
if (canaryLink) { if (canaryLink) {
canaryLink.classList.add('hidden'); canaryLink.classList.add('hidden');
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
width: 100%; width: 100%;
padding-bottom: $gl-padding;
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
display: block; display: block;
......
...@@ -337,8 +337,8 @@ class Project < ApplicationRecord ...@@ -337,8 +337,8 @@ class Project < ApplicationRecord
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_personal_projects_limit, on: :create validate :check_personal_projects_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? } validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :visibility_level_allowed_by_group, if: -> { changes.has_key?(:visibility_level) } validate :visibility_level_allowed_by_group, if: :should_validate_visibility_level?
validate :visibility_level_allowed_as_fork, if: -> { changes.has_key?(:visibility_level) } validate :visibility_level_allowed_as_fork, if: :should_validate_visibility_level?
validate :check_wiki_path_conflict validate :check_wiki_path_conflict
validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) } validate :validate_pages_https_only, if: -> { changes.has_key?(:pages_https_only) }
validates :repository_storage, validates :repository_storage,
...@@ -892,6 +892,10 @@ class Project < ApplicationRecord ...@@ -892,6 +892,10 @@ class Project < ApplicationRecord
self.errors.add(:limit_reached, error % { limit: limit }) self.errors.add(:limit_reached, error % { limit: limit })
end end
def should_validate_visibility_level?
new_record? || changes.has_key?(:visibility_level)
end
def visibility_level_allowed_by_group def visibility_level_allowed_by_group
return if visibility_level_allowed_by_group? return if visibility_level_allowed_by_group?
......
...@@ -488,6 +488,10 @@ class ProjectPolicy < BasePolicy ...@@ -488,6 +488,10 @@ class ProjectPolicy < BasePolicy
def team_access_level def team_access_level
return -1 if @user.nil? return -1 if @user.nil?
lookup_access_level!
end
def lookup_access_level!
# NOTE: max_member_access has its own cache # NOTE: max_member_access has its own cache
project.team.max_member_access(@user.id) project.team.max_member_access(@user.id)
end end
......
...@@ -9,7 +9,7 @@ module Projects ...@@ -9,7 +9,7 @@ module Projects
end end
def execute def execute
Projects::HousekeepingService.new(@project, :gc).execute do Projects::HousekeepingService.new(@project).execute do
repository.delete_all_refs_except(RESERVED_REF_PREFIXES) repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end end
rescue Projects::HousekeepingService::LeaseTaken => e rescue Projects::HousekeepingService::LeaseTaken => e
......
...@@ -37,7 +37,17 @@ module Projects ...@@ -37,7 +37,17 @@ module Projects
raise DownloadLinksError, response.message unless response.success? raise DownloadLinksError, response.message unless response.success?
parse_response_links(response['objects']) # Since the LFS Batch API may return a Content-Ttpe of
# application/vnd.git-lfs+json
# (https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests),
# HTTParty does not know this is actually JSON.
data = JSON.parse(response.body)
raise DownloadLinksError, "LFS Batch API did return any objects" unless data.is_a?(Hash) && data.key?('objects')
parse_response_links(data['objects'])
rescue JSON::ParserError
raise DownloadLinksError, "LFS Batch API response is not JSON"
end end
def parse_response_links(objects_response) def parse_response_links(objects_response)
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
.hide.alert.alert-danger.js-ci-variable-error-box .hide.alert.alert-danger.js-ci-variable-error-box
%ul.ci-variable-list %ul.ci-variable-list
= render 'ci/variables/variable_header'
- @variables.each.each do |variable| - @variables.each.each do |variable|
= render 'ci/variables/variable_row', form_field: 'variables', variable: variable = render 'ci/variables/variable_row', form_field: 'variables', variable: variable
= render 'ci/variables/variable_row', form_field: 'variables' = render 'ci/variables/variable_row', form_field: 'variables'
......
- only_key_value = local_assigns.fetch(:only_key_value, false)
%li.ci-variable-row.m-0.d-none.d-sm-block
.d-flex.w-100.align-items-center.pb-2
.bold.table-section.section-15.append-right-10
= s_('CiVariables|Type')
.bold.table-section.section-15.append-right-10
= s_('CiVariables|Key')
.bold.table-section.section-15.append-right-10
= s_('CiVariables|Value')
- unless only_key_value
.bold.table-section.section-20
= s_('CiVariables|State')
.bold.table-section.section-20
= s_('CiVariables|Masked')
= render_if_exists 'ci/variables/environment_scope_header'
...@@ -20,18 +20,18 @@ ...@@ -20,18 +20,18 @@
- masked_input_name = "#{form_field}[variables_attributes][][masked]" - masked_input_name = "#{form_field}[variables_attributes][][masked]"
%li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } } %li.js-row.ci-variable-row{ data: { is_persisted: "#{!id.nil?}" } }
.ci-variable-row-body .ci-variable-row-body.border-bottom
%input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id } %input.js-ci-variable-input-id{ type: "hidden", name: id_input_name, value: id }
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name } %input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control{ name: variable_type_input_name } %select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.table-section.section-15{ name: variable_type_input_name }
= options_for_select(ci_variable_type_options, variable_type) = options_for_select(ci_variable_type_options, variable_type)
%input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control{ type: "text", %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text",
name: key_input_name, name: key_input_name,
value: key, value: key,
placeholder: s_('CiVariables|Input variable key') } placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item.gl-show-field-errors .ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
.form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) } .form-control.js-secret-value-placeholder.qa-ci-variable-input-value{ class: ('hide' unless id) }
= '*' * 20 = '*' * 17
%textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id), %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ class: ('hide' if id),
rows: 1, rows: 1,
name: value_input_name, name: value_input_name,
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
= s_("CiVariables|Cannot use Masked Variable with current value") = s_("CiVariables|Cannot use Masked Variable with current value")
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer' = link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'masked-variables'), target: '_blank', rel: 'noopener noreferrer'
- unless only_key_value - unless only_key_value
.ci-variable-body-item.ci-variable-protected-item .ci-variable-body-item.ci-variable-protected-item.table-section.section-20.mr-0.border-top-0
.append-right-default .append-right-default
= s_("CiVariable|Protected") = s_("CiVariable|Protected")
%button{ type: 'button', %button{ type: 'button',
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
%span.toggle-icon %span.toggle-icon
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.ci-variable-body-item.ci-variable-masked-item .ci-variable-body-item.ci-variable-masked-item.table-section.section-20.mr-0.border-top-0
.append-right-default .append-right-default
= s_("CiVariable|Masked") = s_("CiVariable|Masked")
%button{ type: 'button', %button{ type: 'button',
...@@ -70,5 +70,5 @@ ...@@ -70,5 +70,5 @@
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
= render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable = render_if_exists 'ci/variables/environment_scope', form_field: form_field, variable: variable
%button.js-row-remove-button.ci-variable-row-remove-button{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') } %button.js-row-remove-button.ci-variable-row-remove-button.table-section.section-5.border-top-0{ type: 'button', 'aria-label': s_('CiVariables|Remove variable row') }
= icon('minus-circle') = icon('minus-circle')
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.branch-info .branch-info
.branch-title .branch-title
= sprite_icon('fork', size: 12) = sprite_icon('fork', size: 12)
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8 qa-branch-name' do
= branch.name = branch.name
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.badge.badge-primary.prepend-left-5 default %span.badge.badge-primary.prepend-left-5 default
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
= time_ago_with_tooltip(member.created_at) = time_ago_with_tooltip(member.created_at)
- if show_roles - if show_roles
- current_resource = @project || @group - current_resource = @project || @group
.controls.member-controls.row .controls.member-controls
- if show_controls && member.source == current_resource - if show_controls && member.source == current_resource
- if member.can_resend_invite? - if member.can_resend_invite?
......
---
title: Fixes next badge being always visible
merge_request:
author:
type: fixed
---
title: Remove non-semantic use of `.row` in member listing controls
merge_request: 28204
author:
type: fixed
---
title: Fix project visibility level validation
merge_request: 28305
author: Peter Marko
type: fixed
---
title: Add EE fixtures to SeedFu list
merge_request: 28241
author:
type: other
---
title: Properly handle LFS Batch API response in project import
merge_request: 28223
author:
type: fixed
# frozen_string_literal: true
if Gitlab.ee?
SeedFu.fixture_paths += %W[ee/db/fixtures ee/db/fixtures/#{Rails.env}]
end
...@@ -157,6 +157,42 @@ For wikis: ...@@ -157,6 +157,42 @@ For wikis:
sudo -u git -H bundle exec rake geo:verification:wiki:reset RAILS_ENV=production sudo -u git -H bundle exec rake geo:verification:wiki:reset RAILS_ENV=production
``` ```
## Reconcile differences with checksum mismatches
If the **primary** and **secondary** nodes have a checksum verification mismatch, the cause may not be apparent. To find the cause of a checksum mismatch:
1. Navigate to the **Admin Area > Projects** dashboard on the **primary** node, find the
project that you want to check the checksum differences and click on the
**Edit** button:
![Projects dashboard](img/checksum-differences-admin-projects.png)
1. On the project admin page get the **Gitaly storage name**, and **Gitaly relative path**:
![Project admin page](img/checksum-differences-admin-project-page.png)
1. Navigate to the project's repository directory on both **primary** and **secondary** nodes. For an installation from source, the path is usually `/home/git/repositories`. For Omnibus installs, the path is usually `/var/opt/gitlab/git-data/repositories`. Note that if `git_data_dirs` is customized, check the directory layout on your server to be sure.
```sh
cd /var/opt/gitlab/git-data/repositories
```
1. Run the following command on the **primary** node, redirecting the output to a file:
```sh
git show-ref --head | grep -E "HEAD|(refs/(heads|tags|keep-around|merge-requests|environments|notes)/)" > primary-node-refs
```
1. Run the following command on the **secondary** node, redirecting the output to a file:
```sh
git show-ref --head | grep -E "HEAD|(refs/(heads|tags|keep-around|merge-requests|environments|notes)/)" > secondary-node-refs
```
1. Copy the files from the previous steps on the same system, and do a diff between the contents:
```sh
diff primary-node-refs secondary-node-refs
```
## Current limitations ## Current limitations
Until [issue #5064][ee-5064] is completed, background verification doesn't cover Until [issue #5064][ee-5064] is completed, background verification doesn't cover
......
...@@ -44,6 +44,7 @@ description: 'Learn how to contribute to GitLab.' ...@@ -44,6 +44,7 @@ description: 'Learn how to contribute to GitLab.'
- [`Gemfile` guidelines](gemfile.md) - [`Gemfile` guidelines](gemfile.md)
- [Pry debugging](pry_debugging.md) - [Pry debugging](pry_debugging.md)
- [Sidekiq debugging](sidekiq_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md)
- [Accessing session data](session.md)
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible - [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
......
# Accessing session data
Session data in GitLab is stored in Redis and can be accessed in a variety of ways.
During a web request, for example:
- Rails provides access to the session from within controllers through [`ActionDispatch::Session`](https://guides.rubyonrails.org/action_controller_overview.html#session).
- Outside of controllers, it is possible to access the session through `Gitlab::Session`.
Outside of a web request it is still possible to access sessions stored in Redis. For example:
- Session IDs and contents can be [looked up directly in Redis](#redis).
- Data about the UserAgent associated with the session can be accessed through `ActiveSession`.
When storing values in a session it is best to:
- Use simple primitives and avoid storing objects to avoid marshaling complications.
- Clean up after unneeded variables to keep memory usage in Redis down.
## Gitlab::Session
Sometimes you might want to persist data in the session instead of another store like the database. `Gitlab::Session` lets you access this without passing the session around extensively. For example, you could access it from within a policy without having to pass the session through to each place permissions are checked from.
The session has a hash-like interface, just like when using it from a controller. There is also `NamespacedSessionStore` for storing key-value data in a hash.
```ruby
# Lookup a value stored in the current session
Gitlab::Session.current[:my_feature]
# Modify the current session stored in redis
Gitlab::Session.current[:my_feature] = value
# Store key-value data namespaced under a key
Gitlab::NamespacedSessionStore.new(:my_feature)[some_key] = value
# Set the session for a block of code, such as for tests
Gitlab::Session.with_session(my_feature: value) do
# Code that uses Session.current[:my_feature]
end
```
## Redis
Session data can be accessed directly through Redis. This can let you check up on a browser session when debugging.
```ruby
# Get a list of sessions
session_ids = Gitlab::Redis::SharedState.with do |redis|
redis.smembers("#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:#{user.id}")
end
# Retrieve a specific session
session_data = Gitlab::Redis::SharedState.with { |redis| redis.get("#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}") }
Marshal.load(session_data)
```
## Getting device information with ActiveSession
The [**Active Sessions** page on a user's profile](../user/profile/active_sessions.md) displays information about the device used to access each session. The methods used there to list sessions can also be useful for development.
```ruby
# Get list of sessions for a given user
# Includes session_id and data from the UserAgent
ActiveSession.list(user)
```
...@@ -448,6 +448,18 @@ sudo -u git cp config/database.yml.postgresql config/database.yml ...@@ -448,6 +448,18 @@ sudo -u git cp config/database.yml.postgresql config/database.yml
# MySQL only: # MySQL only:
sudo -u git cp config/database.yml.mysql config/database.yml sudo -u git cp config/database.yml.mysql config/database.yml
# PostgreSQL only:
# Remove host, username, and password lines from config/database.yml.
# Once modified, the `production` settings will be as follows:
#
# production:
# adapter: postgresql
# encoding: unicode
# database: gitlabhq_production
# pool: 10
#
sudo -u git -H editor config/database.yml
# MySQL and remote PostgreSQL only: # MySQL and remote PostgreSQL only:
# Update username/password in config/database.yml. # Update username/password in config/database.yml.
# You only need to adapt the production settings (first part). # You only need to adapt the production settings (first part).
...@@ -565,6 +577,18 @@ sudo -u git -H editor config.toml ...@@ -565,6 +577,18 @@ sudo -u git -H editor config.toml
For more information about configuring Gitaly see For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly). [doc/administration/gitaly](../administration/gitaly).
### Start Gitaly
Gitaly must be running for the next section.
```sh
gitlab_path=/home/git/gitlab
gitaly_path=/home/git/gitaly
sudo -u git -H $gitlab_path/bin/daemon_with_pidfile $gitlab_path/tmp/pids/gitaly.pid \
$gitaly_path/gitaly $gitaly_path/config.toml >> $gitlab_path/log/gitaly.log 2>&1 &
```
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
```sh ```sh
...@@ -640,6 +664,12 @@ sudo -u git -H yarn install --production --pure-lockfile ...@@ -640,6 +664,12 @@ sudo -u git -H yarn install --production --pure-lockfile
sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
``` ```
If `rake` fails with `JavaScript heap out of memory` error, try to run it with `NODE_OPTIONS` set as follows.
```sh
sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production NODE_OPTIONS="--max_old_space_size=4096"
```
### Start Your GitLab Instance ### Start Your GitLab Instance
```sh ```sh
......
# SalesForce OmniAuth Provider # SalesForce OmniAuth Provider
You can integrate your GitLab instance with [SalesForce](https://www.salesforce.com/) to enable users to login to your GitLab instance with their SalesForce account. You can integrate your GitLab instance with [SalesForce](https://www.salesforce.com/) to enable users to login to your GitLab instance with their SalesForce account.
## Create SalesForce Application ## Create SalesForce Application
To enable SalesForce OmniAuth provider, you must use SalesForce's credentials for your GitLab instance. To enable SalesForce OmniAuth provider, you must use SalesForce's credentials for your GitLab instance.
To get the credentials (a pair of Client ID and Client Secret), you must register an application on UltraAuth. To get the credentials (a pair of Client ID and Client Secret), you must register an application on SalesForces.
1. Sign in to [SalesForce](https://www.salesforce.com/). 1. Sign in to [SalesForce](https://www.salesforce.com/).
1. Navigate to **Platform Tools/Apps** and click on **New Connected App**. 1. Navigate to **Platform Tools/Apps/App Manager** and click on **New Connected App**.
1. Fill in the application details into the following fields: 1. Fill in the application details into the following fields:
- **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive. - **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
...@@ -64,7 +64,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe ...@@ -64,7 +64,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must registe
} }
``` ```
1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the SalesForce connected application page. 1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the SalesForce connected application page.
1. Change `SALESFORCE_CLIENT_SECRET` to the Client Secret from the SalesForce connected application page. 1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the SalesForce connected application page.
![SalesForce App Secret Details](img/salesforce_app_secret_details.png) ![SalesForce App Secret Details](img/salesforce_app_secret_details.png)
1. Save the configuration file. 1. Save the configuration file.
......
...@@ -98,8 +98,7 @@ the group. ...@@ -98,8 +98,7 @@ the group.
NOTE: **Note:** NOTE: **Note:**
Only available on GitLab.com. Only available on GitLab.com.
If you have a Group with a [paid plan](https://about.gitlab.com/pricing/#gitlab-com) on GitLab.com, You can purchase additional CI minutes so your pipelines will not be blocked after you have
then you can purchase additional CI minutes so your pipelines will not be blocked after you have
used all your CI minutes from your main quota. used all your CI minutes from your main quota.
In order to purchase additional minutes, you should follow these steps: In order to purchase additional minutes, you should follow these steps:
......
...@@ -1894,9 +1894,24 @@ msgstr "" ...@@ -1894,9 +1894,24 @@ msgstr ""
msgid "CiVariables|Input variable value" msgid "CiVariables|Input variable value"
msgstr "" msgstr ""
msgid "CiVariables|Key"
msgstr ""
msgid "CiVariables|Masked"
msgstr ""
msgid "CiVariables|Remove variable row" msgid "CiVariables|Remove variable row"
msgstr "" msgstr ""
msgid "CiVariables|State"
msgstr ""
msgid "CiVariables|Type"
msgstr ""
msgid "CiVariables|Value"
msgstr ""
msgid "CiVariable|* (All environments)" msgid "CiVariable|* (All environments)"
msgstr "" msgstr ""
......
...@@ -113,8 +113,8 @@ module QA ...@@ -113,8 +113,8 @@ module QA
has_css?(element_selector_css(name), wait: wait, text: text) has_css?(element_selector_css(name), wait: wait, text: text)
end end
def has_no_element?(name, wait: Capybara.default_max_wait_time) def has_no_element?(name, text: nil, wait: Capybara.default_max_wait_time)
has_no_css?(element_selector_css(name), wait: wait) has_no_css?(element_selector_css(name), wait: wait, text: text)
end end
def has_text?(text) def has_text?(text)
...@@ -129,8 +129,8 @@ module QA ...@@ -129,8 +129,8 @@ module QA
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time) has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
end end
def within_element(name) def within_element(name, text: nil)
page.within(element_selector_css(name)) do page.within(element_selector_css(name), text: text) do
yield yield
end end
end end
......
...@@ -7,6 +7,7 @@ module QA ...@@ -7,6 +7,7 @@ module QA
class Show < Page::Base class Show < Page::Base
view 'app/views/projects/branches/_branch.html.haml' do view 'app/views/projects/branches/_branch.html.haml' do
element :remove_btn element :remove_btn
element :branch_name
end end
view 'app/views/projects/branches/_panel.html.haml' do view 'app/views/projects/branches/_panel.html.haml' do
element :all_branches element :all_branches
...@@ -27,11 +28,9 @@ module QA ...@@ -27,11 +28,9 @@ module QA
finished_loading? finished_loading?
end end
def has_branch_title?(branch_title) def has_no_branch?(branch_name)
within_element(:all_branches) do within_element(:all_branches) do
within(".item-title") do has_no_element?(:branch_name, text: branch_name, wait: Support::Waiter::DEFAULT_MAX_WAIT_TIME)
has_text?(branch_title)
end
end end
end end
...@@ -48,15 +47,6 @@ module QA ...@@ -48,15 +47,6 @@ module QA
click_element(:delete_merged_branches) click_element(:delete_merged_branches)
end end
end end
def wait_for_texts_not_to_be_visible(texts)
text_not_visible = wait do
texts.all? do |text|
has_no_text?(text)
end
end
raise "Expected text(s) #{texts} not to be visible" unless text_not_visible
end
end end
end end
end end
......
...@@ -73,10 +73,9 @@ module QA ...@@ -73,10 +73,9 @@ module QA
Page::Project::Branches::Show.perform do |branches_view| Page::Project::Branches::Show.perform do |branches_view|
branches_view.delete_branch(third_branch) branches_view.delete_branch(third_branch)
expect(branches_view).to have_no_branch(third_branch)
end end
expect(page).not_to have_content(third_branch)
Page::Project::Branches::Show.perform(&:delete_merged_branches) Page::Project::Branches::Show.perform(&:delete_merged_branches)
expect(page).to have_content( expect(page).to have_content(
...@@ -85,8 +84,7 @@ module QA ...@@ -85,8 +84,7 @@ module QA
page.refresh page.refresh
Page::Project::Branches::Show.perform do |branches_view| Page::Project::Branches::Show.perform do |branches_view|
branches_view.wait_for_texts_not_to_be_visible([commit_message_of_second_branch]) expect(branches_view).to have_no_branch(second_branch)
expect(branches_view).not_to have_branch_title(second_branch)
end end
end end
end end
......
...@@ -76,23 +76,18 @@ module QA ...@@ -76,23 +76,18 @@ module QA
super super
end end
def has_element?(name, text: nil, wait: Capybara.default_max_wait_time) def has_element?(name, **kwargs)
found = super found = super
msg = ["has_element? :#{name}"] log_has_element_or_not('has_element?', name, found, **kwargs)
msg << %Q(with text "#{text}") if text
msg << "(wait: #{wait})"
msg << "returned: #{found}"
log(msg.compact.join(' '))
found found
end end
def has_no_element?(name, wait: Capybara.default_max_wait_time) def has_no_element?(name, **kwargs)
found = super found = super
log("has_no_element? :#{name} returned #{found}") log_has_element_or_not('has_no_element?', name, found, **kwargs)
found found
end end
...@@ -149,6 +144,15 @@ module QA ...@@ -149,6 +144,15 @@ module QA
def log(msg) def log(msg)
QA::Runtime::Logger.debug(msg) QA::Runtime::Logger.debug(msg)
end end
def log_has_element_or_not(method, name, found, **kwargs)
msg = ["#{method} :#{name}"]
msg << %Q(with text "#{kwargs[:text]}") if kwargs[:text]
msg << "(wait: #{kwargs[:wait] || Capybara.default_max_wait_time})"
msg << "returned: #{found}"
log(msg.compact.join(' '))
end
end end
end end
end end
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
module QA module QA
module Support module Support
module Waiter module Waiter
DEFAULT_MAX_WAIT_TIME = 60
module_function module_function
def wait(max: 60, interval: 0.1) def wait(max: DEFAULT_MAX_WAIT_TIME, interval: 0.1)
QA::Runtime::Logger.debug("with wait: max #{max}; interval #{interval}") QA::Runtime::Logger.debug("with wait: max #{max}; interval #{interval}")
start = Time.now start = Time.now
......
...@@ -93,7 +93,14 @@ describe QA::Support::Page::Logging do ...@@ -93,7 +93,14 @@ describe QA::Support::Page::Logging do
allow(page).to receive(:has_no_css?).and_return(true) allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element) } expect { subject.has_no_element?(:element) }
.to output(/has_no_element\? :element returned true/).to_stdout_from_any_process .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element? with text' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element, text: "more text") }
.to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
end end
it 'logs has_text?' do it 'logs has_text?' do
......
...@@ -113,7 +113,7 @@ describe('AjaxFormVariableList', () => { ...@@ -113,7 +113,7 @@ describe('AjaxFormVariableList', () => {
it('hides secret values', done => { it('hides secret values', done => {
mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {}); mock.onPatch(VARIABLE_PATCH_ENDPOINT).reply(200, {});
const row = container.querySelector('.js-row:first-child'); const row = container.querySelector('.js-row');
const valueInput = row.querySelector('.js-ci-variable-input-value'); const valueInput = row.querySelector('.js-ci-variable-input-value');
const valuePlaceholder = row.querySelector('.js-secret-value-placeholder'); const valuePlaceholder = row.querySelector('.js-secret-value-placeholder');
......
...@@ -214,6 +214,13 @@ describe Project do ...@@ -214,6 +214,13 @@ describe Project do
expect(project2).not_to be_valid expect(project2).not_to be_valid
end end
it 'validates the visibility' do
expect_any_instance_of(described_class).to receive(:visibility_level_allowed_as_fork).and_call_original
expect_any_instance_of(described_class).to receive(:visibility_level_allowed_by_group).and_call_original
create(:project)
end
describe 'wiki path conflict' do describe 'wiki path conflict' do
context "when the new path has been used by the wiki of other Project" do context "when the new path has been used by the wiki of other Project" do
it 'has an error on the name attribute' do it 'has an error on the name attribute' do
......
...@@ -15,7 +15,7 @@ describe Projects::AfterImportService do ...@@ -15,7 +15,7 @@ describe Projects::AfterImportService do
describe '#execute' do describe '#execute' do
before do before do
allow(Projects::HousekeepingService) allow(Projects::HousekeepingService)
.to receive(:new).with(project, :gc).and_return(housekeeping_service) .to receive(:new).with(project).and_return(housekeeping_service)
allow(housekeeping_service) allow(housekeeping_service)
.to receive(:execute).and_yield .to receive(:execute).and_yield
......
...@@ -33,7 +33,10 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do ...@@ -33,7 +33,10 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
before do before do
allow(project).to receive(:lfs_enabled?).and_return(true) allow(project).to receive(:lfs_enabled?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(objects_response) response = instance_double(HTTParty::Response)
allow(response).to receive(:body).and_return(objects_response.to_json)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
end end
describe '#execute' do describe '#execute' do
...@@ -89,6 +92,21 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do ...@@ -89,6 +92,21 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError) expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError)
end end
shared_examples 'JSON parse errors' do |body|
it 'raises error' do
response = instance_double(HTTParty::Response)
allow(response).to receive(:body).and_return(body)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
expect { subject.send(:get_download_links, new_oids) }.to raise_error(described_class::DownloadLinksError)
end
end
it_behaves_like 'JSON parse errors', '{'
it_behaves_like 'JSON parse errors', '{}'
it_behaves_like 'JSON parse errors', '{ foo: 123 }'
end end
describe '#parse_response_links' do describe '#parse_response_links' do
......
...@@ -17,7 +17,7 @@ shared_examples 'variable list' do ...@@ -17,7 +17,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh # We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-key').value).to eq('key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
end end
...@@ -38,7 +38,7 @@ shared_examples 'variable list' do ...@@ -38,7 +38,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh # We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-key').value).to eq('key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
...@@ -59,7 +59,7 @@ shared_examples 'variable list' do ...@@ -59,7 +59,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh # We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('key') expect(find('.js-ci-variable-input-key').value).to eq('key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('key_value')
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
...@@ -116,19 +116,19 @@ shared_examples 'variable list' do ...@@ -116,19 +116,19 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section') do page.within('.js-ci-variable-list-section') do
expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
expect(page).to have_content('*' * 20) expect(page).to have_content('*' * 17)
click_button('Reveal value') click_button('Reveal value')
expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value').value).to eq(variable.value) expect(first('.js-ci-variable-input-value').value).to eq(variable.value)
expect(page).not_to have_content('*' * 20) expect(page).not_to have_content('*' * 17)
click_button('Hide value') click_button('Hide value')
expect(first('.js-ci-variable-input-key').value).to eq(variable.key) expect(first('.js-ci-variable-input-key').value).to eq(variable.key)
expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value) expect(first('.js-ci-variable-input-value', visible: false).value).to eq(variable.value)
expect(page).to have_content('*' * 20) expect(page).to have_content('*' * 17)
end end
end end
...@@ -149,7 +149,7 @@ shared_examples 'variable list' do ...@@ -149,7 +149,7 @@ shared_examples 'variable list' do
page.within('.js-ci-variable-list-section') do page.within('.js-ci-variable-list-section') do
click_button('Reveal value') click_button('Reveal value')
page.within('.js-row:nth-child(1)') do page.within('.js-row:nth-child(2)') do
find('.js-ci-variable-input-key').set('new_key') find('.js-ci-variable-input-key').set('new_key')
find('.js-ci-variable-input-value').set('new_value') find('.js-ci-variable-input-value').set('new_value')
end end
...@@ -159,7 +159,7 @@ shared_examples 'variable list' do ...@@ -159,7 +159,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-row:nth-child(1)') do page.within('.js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('new_key') expect(find('.js-ci-variable-input-key').value).to eq('new_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('new_value')
end end
...@@ -181,7 +181,7 @@ shared_examples 'variable list' do ...@@ -181,7 +181,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh # We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
find('.ci-variable-protected-item .js-project-feature-toggle').click find('.ci-variable-protected-item .js-project-feature-toggle').click
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
...@@ -193,7 +193,7 @@ shared_examples 'variable list' do ...@@ -193,7 +193,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
# We check the first row because it re-sorts to alphabetical order on refresh # We check the first row because it re-sorts to alphabetical order on refresh
page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do page.within('.js-ci-variable-list-section .js-row:nth-child(3)') do
expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key') expect(find('.js-ci-variable-input-key').value).to eq('unprotected_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('unprotected_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('true')
...@@ -215,7 +215,7 @@ shared_examples 'variable list' do ...@@ -215,7 +215,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
find('.ci-variable-protected-item .js-project-feature-toggle').click find('.ci-variable-protected-item .js-project-feature-toggle').click
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
...@@ -226,7 +226,7 @@ shared_examples 'variable list' do ...@@ -226,7 +226,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-key').value).to eq('protected_key') expect(find('.js-ci-variable-input-key').value).to eq('protected_key')
expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value') expect(find('.js-ci-variable-input-value', visible: false).value).to eq('protected_value')
expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false') expect(find('.js-ci-variable-input-protected', visible: false).value).to eq('false')
...@@ -234,7 +234,7 @@ shared_examples 'variable list' do ...@@ -234,7 +234,7 @@ shared_examples 'variable list' do
end end
it 'edits variable to be unmasked' do it 'edits variable to be unmasked' do
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
find('.ci-variable-masked-item .js-project-feature-toggle').click find('.ci-variable-masked-item .js-project-feature-toggle').click
...@@ -247,13 +247,13 @@ shared_examples 'variable list' do ...@@ -247,13 +247,13 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
end end
end end
it 'edits variable to be masked' do it 'edits variable to be masked' do
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
find('.ci-variable-masked-item .js-project-feature-toggle').click find('.ci-variable-masked-item .js-project-feature-toggle').click
...@@ -266,7 +266,7 @@ shared_examples 'variable list' do ...@@ -266,7 +266,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('false')
find('.ci-variable-masked-item .js-project-feature-toggle').click find('.ci-variable-masked-item .js-project-feature-toggle').click
...@@ -279,7 +279,7 @@ shared_examples 'variable list' do ...@@ -279,7 +279,7 @@ shared_examples 'variable list' do
visit page_path visit page_path
page.within('.js-ci-variable-list-section .js-row:nth-child(1)') do page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true') expect(find('.js-ci-variable-input-masked', visible: false).value).to eq('true')
end end
end end
...@@ -302,7 +302,7 @@ shared_examples 'variable list' do ...@@ -302,7 +302,7 @@ shared_examples 'variable list' do
expect(page).to have_selector('.js-row', count: 4) expect(page).to have_selector('.js-row', count: 4)
# Remove the `akey` variable # Remove the `akey` variable
page.within('.js-row:nth-child(2)') do page.within('.js-row:nth-child(3)') do
first('.js-row-remove-button').click first('.js-row-remove-button').click
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment