Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-27 09:09:26 +00:00
parent 0d0fdf863c
commit 1f84ff323d
20 changed files with 291 additions and 59 deletions

View file

@ -73,8 +73,7 @@ populate-qa-tests-var:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: prepare
script:
- tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
- '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"'
- export QA_TESTS=$(scripts/determine-qa-tests --files $CHANGES_FILE --labels $CI_MERGE_REQUEST_LABELS)
- 'echo "QA_TESTS=$QA_TESTS" >> qa_tests_var.env'
- 'echo "QA_TESTS: $QA_TESTS"'
artifacts:
@ -83,11 +82,9 @@ populate-qa-tests-var:
dotenv: qa_tests_var.env
paths:
- ${CHANGES_FILE}
- ${ONLY_QA_CHANGES_FILE}
- qa_tests_var.env
variables:
CHANGES_FILE: tmp/changed_files.txt
ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
needs:
- detect-tests

View file

@ -127,6 +127,9 @@
.if-dot-com-gitlab-org-and-security-merge-request: &if-dot-com-gitlab-org-and-security-merge-request
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_MERGE_REQUEST_IID'
.if-dot-com-gitlab-org-and-security-merge-request-and-qa-tests-specified: &if-dot-com-gitlab-org-and-security-merge-request-and-qa-tests-specified
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_MERGE_REQUEST_IID && $QA_TESTS'
.if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-qa: &if-dot-com-gitlab-org-and-security-merge-request-manual-ff-package-and-qa
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE =~ /^gitlab-org($|\/security$)/ && $CI_MERGE_REQUEST_IID && $QA_MANUAL_FF_PACKAGE_AND_QA'
@ -943,6 +946,9 @@
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request-and-qa-tests-specified
changes: *code-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
changes: *code-patterns
when: manual

View file

@ -6547,7 +6547,6 @@ Layout/LineLength:
- 'spec/workers/users/deactivate_dormant_users_worker_spec.rb'
- 'spec/workers/web_hooks/destroy_worker_spec.rb'
- 'tooling/bin/find_changes'
- 'tooling/bin/qa/check_if_qa_only_spec_changes'
- 'tooling/danger/product_intelligence.rb'
- 'tooling/danger/project_helper.rb'
- 'tooling/danger/specs.rb'

View file

@ -1251,7 +1251,6 @@ Style/IfUnlessModifier:
- 'spec/views/projects/edit.html.haml_spec.rb'
- 'spec/views/shared/access_tokens/_table.html.haml_spec.rb'
- 'spec/workers/analytics/usage_trends/counter_job_worker_spec.rb'
- 'tooling/bin/qa/check_if_qa_only_spec_changes'
- 'tooling/danger/product_intelligence.rb'
- 'tooling/lib/tooling/image.rb'
- 'tooling/lib/tooling/test_map_packer.rb'

View file

@ -109,5 +109,4 @@ Style/RedundantRegexpEscape:
- 'spec/support_specs/matchers/exceed_query_limit_helpers_spec.rb'
- 'spec/uploaders/personal_file_uploader_spec.rb'
- 'spec/views/help/index.html.haml_spec.rb'
- 'tooling/bin/qa/check_if_qa_only_spec_changes'
- 'tooling/danger/project_helper.rb'

View file

@ -58,7 +58,9 @@ class UsersController < ApplicationController
# Get all keys of a user(params[:username]) in a text format
# Helpful for sysadmins to put in respective servers
def ssh_keys
render plain: user.all_ssh_keys.join("\n")
keys = user.all_ssh_keys.join("\n")
keys << "\n" unless keys.empty?
render plain: keys
end
def activity
@ -74,7 +76,9 @@ class UsersController < ApplicationController
# Get all gpg keys of a user(params[:username]) in a text format
def gpg_keys
render plain: user.gpg_keys.select(&:verified?).map(&:key).join("\n")
keys = user.gpg_keys.filter_map { |gpg_key| gpg_key.key if gpg_key.verified? }.join("\n")
keys << "\n" unless keys.empty?
render plain: keys
end
def groups

View file

@ -316,7 +316,12 @@ class IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord
def by_project(items)
if params.project? || params.projects
# When finding issues for multiple projects it's more efficient
# to use a JOIN instead of running a sub-query
# See https://gitlab.com/gitlab-org/gitlab/-/commit/8591cc02be6b12ed60f763a5e0147f2cbbca99e1
if params.projects.is_a?(ActiveRecord::Relation)
items.merge(params.projects.reorder(nil)).join_project
elsif params.projects
items.of_projects(params.projects).references_project
else
items.none

View file

@ -15,7 +15,7 @@
- else
= _('Change your password or recover your current one')
= form_for @user, url: profile_password_path, method: :put, html: {class: "update-password"} do |f|
= form_errors(@user)
= form_errors(@user, pajamas_alert: true)
- unless @user.password_automatically_set?
.form-group

View file

@ -31,22 +31,19 @@
%ul
- if label.project_label? && label.project.group && can?(current_user, :admin_label, label.project.group)
%li
%button.js-promote-project-label-button.gl-button.btn.btn-default-tertiary{ disabled: true, type: 'button',
data: { url: promote_project_label_path(label.project, label),
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
group_name: label.project.group.name } }
= render Pajamas::ButtonComponent.new(category: :tertiary,
button_options: { class: 'js-promote-project-label-button', data: { url: promote_project_label_path(label.project, label), label_title: label.title, label_color: label.color, label_text_color: label.text_color, group_name: label.project.group.name } } ) do
= _('Promote to group label')
%li
%span
%button.text-danger.js-delete-label-modal-button{ type: 'button', data: { label_name: label.name, subject_name: label.subject_name, destroy_path: label.destroy_path } }
= render Pajamas::ButtonComponent.new(category: :tertiary,
button_options: { class: 'text-danger js-delete-label-modal-button', data: { label_name: label.name, subject_name: label.subject_name, destroy_path: label.destroy_path } } ) do
= _('Delete')
- if current_user
%li.gl-display-inline-block.label-subscription.js-label-subscription.gl-ml-3
- if label.can_subscribe_to_label_in_different_levels?
%button.js-unsubscribe-button.gl-button.btn.btn-default.gl-w-full{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span.gl-button-text= _('Unsubscribe')
= render Pajamas::ButtonComponent.new(button_options: { class: "js-unsubscribe-button #{('hidden' if status.unsubscribed?)}", data: { url: toggle_subscription_path, toggle: 'tooltip', container: 'body' }, title: tooltip_title } ) do
= _('Unsubscribe')
.dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) }
= render Pajamas::ButtonComponent.new(button_options: { class: 'gl-w-full', data: { toggle: 'dropdown' } }) do
= _('Subscribe')
@ -54,11 +51,11 @@
.dropdown-menu.dropdown-open-left
%ul
%li
%button.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } }
%span.gl-button-text= _('Subscribe at project level')
= render Pajamas::ButtonComponent.new(category: :tertiary, button_options: { class: "js-subscribe-button #{('hidden' unless status.unsubscribed?)}", data: { status: status, url: toggle_subscription_project_label_path(@project, label) } } ) do
= _('Subscribe at project level')
%li
%button.js-subscribe-button.js-group-level{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } }
%span.gl-button-text= _('Subscribe at group level')
= render Pajamas::ButtonComponent.new(category: :tertiary, button_options: { class: "js-subscribe-button js-group-level #{('hidden' unless status.unsubscribed?)}", data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } } ) do
= _('Subscribe at group level')
- else
%button.gl-button.js-subscribe-button.btn.btn-default.gl-w-full{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
%span.gl-button-text= label_subscription_toggle_button_text(label, @project)
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-subscribe-button gl-w-full', data: { status: status, url: toggle_subscription_path, toggle: 'tooltip', container: 'body' }, title: tooltip_title } ) do
= label_subscription_toggle_button_text(label, @project)

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class RemoveTmpIndexOnRoutesNamespaceIdColumn < Gitlab::Database::Migration[2.0]
INDEX_NAME = 'tmp_index_for_namespace_id_migration_on_routes'
disable_ddl_transaction!
def up
remove_concurrent_index_by_name :routes, INDEX_NAME
end
def down
add_concurrent_index :routes,
:id,
where: "routes.namespace_id is null and routes.source_type = 'Namespace'",
name: INDEX_NAME
end
end

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
class ValidateNotNullConstraintOnRoutesNamespaceIdColumn < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
validate_not_null_constraint :routes, :namespace_id
end
def down
remove_not_null_constraint :routes, :namespace_id
add_not_null_constraint :routes, :namespace_id, validate: false
end
end

View file

@ -0,0 +1 @@
290ff026a11ac5eadd71fb9fb3ba21bed535d148c56b3176f115e973cdb41369

View file

@ -0,0 +1 @@
c414443040bb168009bcbe00f874b1f474c1d1dcf563e621cfdf641f21846d59

View file

@ -20349,7 +20349,8 @@ CREATE TABLE routes (
created_at timestamp without time zone,
updated_at timestamp without time zone,
name character varying,
namespace_id bigint
namespace_id bigint,
CONSTRAINT check_af84c6c93f CHECK ((namespace_id IS NOT NULL))
);
CREATE SEQUENCE routes_id_seq
@ -24302,9 +24303,6 @@ ALTER TABLE ONLY chat_teams
ALTER TABLE vulnerability_scanners
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE routes
ADD CONSTRAINT check_af84c6c93f CHECK ((namespace_id IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;
@ -29997,8 +29995,6 @@ CREATE INDEX tmp_index_for_namespace_id_migration_on_group_members ON members US
CREATE INDEX tmp_index_for_namespace_id_migration_on_project_members ON members USING btree (id) WHERE ((member_namespace_id IS NULL) AND ((type)::text = 'ProjectMember'::text));
CREATE INDEX tmp_index_for_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Namespace'::text));
CREATE INDEX tmp_index_for_null_project_namespace_id ON projects USING btree (id) WHERE (project_namespace_id IS NULL);
CREATE INDEX tmp_index_for_project_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Project'::text));

View file

@ -41232,6 +41232,9 @@ msgstr ""
msgid "UsageQuota|Something went wrong while fetching project storage statistics"
msgstr ""
msgid "UsageQuota|Something went wrong while loading usage details"
msgstr ""
msgid "UsageQuota|Storage"
msgstr ""

106
scripts/determine-qa-tests Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
# This script returns end-to-end tests or test directories. The returned value is stored in QA_TESTS
# variable for executing only those tests.
class DetermineQATests # rubocop:disable Gitlab/NamespacedClass
def initialize(options)
@changed_files = options.delete(:changed_files)
@mr_labels = options.delete(:mr_labels) || []
end
def execute
# If only e2e test files have changed, run only those tests
qa_tests = if has_qa_spec_only_changes?
changed_files
# If only non qa files have changed, use the devops MR label to run the files in the related directory
# However, if a feature flag file has changed, do not return any specific test/test directory
elsif has_non_qa_only_changes? && mr_labels.any? && !has_dev_ops_feature_flag_changes?
devops_stage = devops_stage_from_mr_labels
qa_spec_directories_for_devops_stage(devops_stage) if devops_stage
end
trim_path(qa_tests).join(' ') if qa_tests
end
private
attr_reader :changed_files, :mr_labels
# Are the changed files only qa specs?
#
# @return [Boolean] whether the changes files are only qa specs
def has_qa_spec_only_changes?
changed_files.all? { |file_path| file_path =~ %r{^qa/qa/specs/features/} }
end
# Are the changed files only outside the qa directory?
#
# @return [Boolean] whether the changes files are outside of qa directory
def has_non_qa_only_changes?
changed_files.none? { |file_path| file_path =~ %r{^qa/} }
end
# Are the changed files for development and ops feature flags?
#
# @return [Boolean] whether the changes files are for development and ops feature flags
def has_dev_ops_feature_flag_changes?
changed_files.any? { |file_path| file_path =~ %r{/feature_flags/(development|ops)/.*\.yml} }
end
# Remove the leading `qa/` from the file or directory paths
#
# @param [Array] paths Array of file or directory paths
# @return [Array] Array of files or directories with the first occurance of `qa/` removed
def trim_path(paths)
paths.map { |path| path.delete_prefix("qa/") }
end
# Extract devops stage from MR labels
#
# @return [String] a devops stage
def devops_stage_from_mr_labels
mr_labels.find { |label| label =~ /^devops::/ }&.delete_prefix('devops::')
end
# Get qa spec directories for devops stage
#
# @param [String] devops_stage a devops stage
# @return [Array] qa spec directories
def qa_spec_directories_for_devops_stage(devops_stage)
Dir.glob("qa/qa/specs/**/*/").select { |dir| dir =~ %r{\d+_#{devops_stage}/$} }
end
end
if $0 == __FILE__
options = {}
OptionParser.new do |opts|
opts.on("-f", "--files CHANGED_FILES_PATH", String,
"A path to a file containing a list of changed files") do |value|
changed_files_path = value
abort("ERROR: The specified changed files path does not exist") unless File.exist?(changed_files_path)
changed_files = File.read(changed_files_path).split(' ')
abort("ERROR: There are no changed files") if changed_files.empty?
options[:changed_files] = changed_files
end
opts.on("-l", "--labels MR_LABELS", String, "A comma separated list of MR labels") do |value|
options[:mr_labels] = Array(value&.split(',')).compact
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
puts DetermineQATests.new(options).execute
end

View file

@ -25,7 +25,7 @@ RSpec.describe 'Profile > Password' do
it 'shows an error message' do
fill_passwords('mypassword', 'mypassword2')
page.within('.alert-danger') do
page.within('.gl-alert-danger') do
expect(page).to have_content("Password confirmation doesn't match Password")
end
end

View file

@ -236,14 +236,14 @@ RSpec.describe UsersController do
let!(:deploy_key) { create(:deploy_key, user: user) }
shared_examples_for 'renders all public keys' do
it 'renders all non-deploy keys separated with a new line with text/plain content type without the comment key' do
it 'renders all non-deploy keys terminated with a new line with text/plain content type without the comment key' do
get "/#{user.username}.keys"
expect(response).to be_successful
expect(response.media_type).to eq("text/plain")
expect(response.body).not_to eq('')
expect(response.body).to eq(user.all_ssh_keys.join("\n"))
expect(response.body).to eq(user.all_ssh_keys.map { |key| key + "\n" }.join)
expect(response.body).to include(key.key.sub(' dummy@gitlab.com', ''))
expect(response.body).to include(another_key.key.sub(' dummy@gitlab.com', ''))
@ -308,7 +308,7 @@ RSpec.describe UsersController do
let!(:another_gpg_key) { create(:another_gpg_key, user: user.reload) }
shared_examples_for 'renders all verified GPG keys' do
it 'renders all verified keys separated with a new line with text/plain content type' do
it 'renders all verified keys terminated with a new line with text/plain content type' do
get "/#{user.username}.gpg"
expect(response).to be_successful
@ -316,7 +316,7 @@ RSpec.describe UsersController do
expect(response.media_type).to eq("text/plain")
expect(response.body).not_to eq('')
expect(response.body).to eq(user.gpg_keys.select(&:verified?).map(&:key).join("\n"))
expect(response.body).to eq(user.gpg_keys.filter_map { |gpg_key| gpg_key.key + "\n" if gpg_key.verified? }.join)
expect(response.body).to include(gpg_key.key)
expect(response.body).to include(another_gpg_key.key)

View file

@ -0,0 +1,109 @@
# frozen_string_literal: true
require 'fast_spec_helper'
load File.expand_path('../../scripts/determine-qa-tests', __dir__)
RSpec.describe 'scripts/determine-qa-tests' do
describe DetermineQATests do
describe '.execute' do
let(:qa_spec_files) do
%w[qa/qa/specs/features/browser_ui/1_manage/test1.rb
qa/qa/specs/features/browser_ui/1_manage/user/test2.rb]
end
let(:qa_spec_and_non_spec_files) do
%w[qa/qa/specs/features/browser_ui/1_manage/test1.rb
qa/qa/page/admin/menu.rb]
end
let(:non_qa_files) do
%w[rubocop/code_reuse_helpers.rb
app/components/diffs/overflow_warning_component.rb]
end
let(:non_qa_and_feature_flag_files) do
%w[rubocop/code_reuse_helpers.rb
app/components/diffs/overflow_warning_component.rb
config/feature_flags/development/access_token_ajax.yml]
end
let(:qa_spec_and_non_qa_files) do
%w[rubocop/code_reuse_helpers.rb
app/components/diffs/overflow_warning_component.rb
qa/qa/specs/features/browser_ui/1_manage/test1.rb]
end
let(:qa_non_spec_and_non_qa_files) do
%w[rubocop/code_reuse_helpers.rb
app/components/diffs/overflow_warning_component.rb
qa/qa/page/admin/menu.rb]
end
shared_examples 'determine qa tests' do
context 'when only qa spec files have changed' do
it 'returns only the changed qa specs' do
subject = described_class.new({ changed_files: qa_spec_files }.merge(labels))
expect(subject.execute).to eql qa_spec_files.map { |path| path.delete_prefix("qa/") }.join(' ')
end
end
context 'when qa spec and non spec files have changed' do
it 'does not return any specs' do
subject = described_class.new({ changed_files: qa_spec_and_non_spec_files }.merge(labels))
expect(subject.execute).to be_nil
end
end
context 'when non-qa and feature flag files have changed' do
it 'does not return any specs' do
subject = described_class.new({ changed_files: non_qa_and_feature_flag_files }.merge(labels))
expect(subject.execute).to be_nil
end
end
context 'when qa spec and non-qa files have changed' do
it 'does not return any specs' do
subject = described_class.new({ changed_files: qa_spec_and_non_qa_files }.merge(labels))
expect(subject.execute).to be_nil
end
end
context 'when qa non-spec and non-qa files have changed' do
it 'does not return any specs' do
subject = described_class.new({ changed_files: qa_non_spec_and_non_qa_files }.merge(labels))
expect(subject.execute).to be_nil
end
end
end
context 'when a devops label is not specified' do
let(:labels) { { mr_labels: ['type::feature'] } }
it_behaves_like 'determine qa tests'
context 'when only non-qa files have changed' do
it 'does not return any specs' do
subject = described_class.new({ changed_files: non_qa_files })
expect(subject.execute).to be_nil
end
end
end
context 'when a devops label is specified' do
let(:labels) { { mr_labels: %w[devops::manage type::feature] } }
it_behaves_like 'determine qa tests'
context 'when only non-qa files have changed' do
it 'returns the specs for the devops label' do
subject = described_class.new({ changed_files: non_qa_files }.merge(labels))
allow(subject).to receive(:qa_spec_directories_for_devops_stage)
.and_return(['qa/qa/specs/features/browser_ui/1_manage/'])
expect(subject.execute).to eql 'qa/specs/features/browser_ui/1_manage/'
end
end
end
end
end
end

View file

@ -1,22 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# This script assumes the first argument is a path to a file containing a list of changed files and the second argument
# is the path of a file where a list of end-to-end spec files with the leading 'qa/' trimmed will be written to if
# all the files are end-to-end test spec files.
abort("ERROR: Please specify the file containing the list of changed files and a file where the qa only spec files will be written") if ARGV.size != 2
changed_files_path = ARGV.shift
output_file = ARGV.shift
return unless File.exist?(changed_files_path)
changed_files = File.read(changed_files_path).split(' ')
all_files_are_qa_specs = changed_files.all? { |file_path| file_path =~ %r{^qa\/qa\/specs\/features\/} }
if all_files_are_qa_specs
qa_spec_paths_trimmed = changed_files.map { |path| path.sub('qa/', '') }
File.write(output_file, qa_spec_paths_trimmed.join(' '))
end