Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-28 15:09:17 +00:00
parent eea806d673
commit 5bb54b8711
39 changed files with 375 additions and 360 deletions

View File

@ -328,7 +328,6 @@
- "danger/**/*"
- "{,ee/,jh/}fixtures/**/*"
- "{,ee/,jh/}rubocop/**/*"
- ".rubocop_todo/**/*.yml"
- "{,ee/,jh/}spec/**/*"
- "{,spec/}tooling/**/*"
@ -346,8 +345,7 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo}.yml"
- ".rubocop_todo/**/*.yml"
- ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@ -371,8 +369,7 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo}.yml"
- ".rubocop_todo/**/*.yml"
- ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@ -403,8 +400,7 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo}.yml"
- ".rubocop_todo/**/*.yml"
- ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@ -431,8 +427,7 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo}.yml"
- ".rubocop_todo/**/*.yml"
- ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@ -466,8 +461,7 @@
- "Dockerfile.assets"
- "vendor/assets/**/*"
- ".{eslintignore,gitattributes,nvmrc,prettierrc,stylelintrc,yamllint}"
- ".{codeclimate,eslintrc,gitlab-ci,haml-lint,haml-lint_todo,rubocop,rubocop_todo}.yml"
- ".rubocop_todo/**/*.yml"
- ".gitlab-ci.yml"
- "*_VERSION"
- "{,jh/}Gemfile{,.lock}"
- "Rakefile"
@ -497,6 +491,12 @@
- "scripts/lint_templates_bash.rb"
- "lib/gitlab/ci/templates/**/*.gitlab-ci.yml"
.static-analysis-patterns: &static-analysis-patterns
- ".{codeclimate,eslintrc,haml-lint,haml-lint_todo}.yml"
- ".rubocop.yml"
- ".rubocop_todo.yml"
- ".rubocop_todo/**/*.yml"
.danger-patterns: &danger-patterns
- "Dangerfile"
- "danger/**/*"
@ -1440,16 +1440,19 @@
.static-analysis:rules:ee-and-foss:
rules:
- changes: *code-backstage-qa-patterns
- changes: *static-analysis-patterns
.static-analysis:rules:ee-and-foss-qa:
rules:
- changes: *qa-patterns
- changes: *static-analysis-patterns
.static-analysis:rules:ee:
rules:
- <<: *if-not-ee
when: never
- changes: *code-backstage-qa-patterns
- changes: *static-analysis-patterns
.static-analysis:rules:as-if-foss:
rules:
@ -1461,6 +1464,8 @@
changes: *code-backstage-qa-patterns
- <<: *if-merge-request
changes: *ci-patterns
- <<: *if-merge-request
changes: *static-analysis-patterns
.semgrep-appsec-custom-rules:rules:
rules:

View File

@ -268,7 +268,7 @@ gem 'sanitize', '~> 6.0'
gem 'babosa', '~> 1.0.4'
# Sanitizes SVG input
gem 'loofah', '~> 2.2'
gem 'loofah', '~> 2.18.0'
# Working with license
gem 'licensee', '~> 9.14.1'
@ -344,7 +344,7 @@ gem 'prometheus-client-mmap', '~> 0.15.0', require: 'prometheus/client'
gem 'warning', '~> 1.2.0'
group :development do
gem 'lefthook', '~> 1.0.1', require: false
gem 'lefthook', '~> 1.0.0', require: false
gem 'rubocop'
gem 'solargraph', '~> 0.44.3', require: false

View File

@ -724,7 +724,7 @@ GEM
rest-client (~> 2.0)
launchy (2.5.0)
addressable (~> 2.7)
lefthook (1.0.1)
lefthook (1.0.2)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (2.0.0)
@ -749,7 +749,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.16.0)
loofah (2.18.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
@ -1586,12 +1586,12 @@ DEPENDENCIES
knapsack (~> 1.21.1)
kramdown (~> 2.3.1)
kubeclient (~> 4.9.2)
lefthook (~> 1.0.1)
lefthook (~> 1.0.0)
letter_opener_web (~> 2.0.0)
licensee (~> 9.14.1)
lockbox (~> 0.6.2)
lograge (~> 0.5)
loofah (~> 2.2)
loofah (~> 2.18.0)
lru_redux
mail (= 2.7.1)
mail-smtp_pool (~> 0.1.0)!

View File

@ -1,4 +1,4 @@
/* eslint-disable func-names, consistent-return, no-return-assign, @gitlab/require-i18n-strings */
/* eslint-disable func-names, no-return-assign, @gitlab/require-i18n-strings */
import $ from 'jquery';
import RefSelectDropdown from './ref_select_dropdown';
@ -6,9 +6,9 @@ import RefSelectDropdown from './ref_select_dropdown';
export default class NewBranchForm {
constructor(form, availableRefs) {
this.validate = this.validate.bind(this);
this.branchNameError = form.find('.js-branch-name-error');
this.name = form.find('.js-branch-name');
this.ref = form.find('#ref');
this.branchNameError = form.querySelector('.js-branch-name-error');
this.name = form.querySelector('.js-branch-name');
this.ref = form.querySelector('#ref');
new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new
this.setupRestrictions();
this.addBinding();
@ -16,12 +16,13 @@ export default class NewBranchForm {
}
addBinding() {
return this.name.on('blur', this.validate);
this.name.addEventListener('blur', this.validate);
}
init() {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
if (this.name != null && this.name.value.length > 0) {
const event = new CustomEvent('blur');
this.name.dispatchEvent(event);
}
}
@ -52,7 +53,7 @@ export default class NewBranchForm {
validate() {
const { indexOf } = [];
this.branchNameError.empty();
this.branchNameError.innerHTML = '';
const unique = function (values, value) {
if (indexOf.call(values, value) === -1) {
values.push(value);
@ -73,7 +74,7 @@ export default class NewBranchForm {
return `${restriction.prefix} ${formatted.join(restriction.conjunction)}`;
};
const validator = (errors, restriction) => {
const matched = this.name.val().match(restriction.pattern);
const matched = this.name.value.match(restriction.pattern);
if (matched) {
return errors.concat(formatter(matched.reduce(unique, []), restriction));
}
@ -81,8 +82,7 @@ export default class NewBranchForm {
};
const errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
const errorMessage = $('<span/>').text(errors.join(', '));
return this.branchNameError.append(errorMessage);
this.branchNameError.textContent = errors.join(', ');
}
}
}

View File

@ -1,8 +1,7 @@
import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
// eslint-disable-next-line no-new
new NewBranchForm(
$('.js-create-branch-form'),
document.querySelector('.js-create-branch-form'),
JSON.parse(document.getElementById('availableRefs').innerHTML),
);

View File

@ -22,7 +22,7 @@ class Import::FogbugzController < Import::BaseController
session[:fogbugz_token] = res.get_token
session[:fogbugz_uri] = params[:uri]
redirect_to new_user_map_import_fogbugz_path
redirect_to new_user_map_import_fogbugz_path(namespace_id: params[:namespace_id])
end
def new_user_map
@ -41,12 +41,12 @@ class Import::FogbugzController < Import::BaseController
flash[:notice] = _('The user map has been saved. Continue by selecting the projects you want to import.')
redirect_to status_import_fogbugz_path
redirect_to status_import_fogbugz_path(namespace_id: params[:namespace_id])
end
def status
unless client.valid?
return redirect_to new_import_fogbugz_path
return redirect_to new_import_fogbugz_path(namespace_id: params[:namespace_id])
end
super
@ -106,7 +106,7 @@ class Import::FogbugzController < Import::BaseController
end
def fogbugz_unauthorized(exception)
redirect_to new_import_fogbugz_path, alert: exception.message
redirect_to new_import_fogbugz_path(namespace_id: params[:namespace_id]), alert: exception.message
end
def import_params

View File

@ -39,9 +39,5 @@ module Ci
def suite_error
tests.dig("suite_error")
end
def tests_total
[tests_success, tests_failed, tests_errored, tests_skipped].sum
end
end
end

View File

@ -8,7 +8,7 @@
= _('Import projects from FogBugz')
%hr
= form_tag callback_import_fogbugz_path do
= form_tag callback_import_fogbugz_path(namespace_id: params[:namespace_id]) do
%p
= _("To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import.")
.form-group.row

View File

@ -8,7 +8,7 @@
= _('Import projects from FogBugz')
%hr
= form_tag create_user_map_import_fogbugz_path do
= form_tag create_user_map_import_fogbugz_path(namespace_id: params[:namespace_id]) do
%p
= _("Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import.")
%p

View File

@ -8,4 +8,4 @@
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
= render 'import/githubish_status', provider: 'fogbugz', filterable: false
= render 'import/githubish_status', provider: 'fogbugz', filterable: false, default_namespace: @namespace

View File

@ -46,7 +46,7 @@
- if fogbugz_import_enabled?
%div
= link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } do
= link_to new_import_fogbugz_path(namespace_id: namespace_id), class: 'gl-button btn-default btn import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } do
.gl-button-icon
= sprite_icon('bug')
FogBugz

View File

@ -11,23 +11,21 @@
.milestone-buttons
- if can?(current_user, :admin_milestone, @group || @project)
= link_to _('Edit'), edit_milestone_path(milestone), class: 'btn gl-button btn-grouped'
= render Pajamas::ButtonComponent.new(href: edit_milestone_path(milestone), button_options: { class: 'btn-grouped' }) do
= _('Edit')
- if milestone.project_milestone? && milestone.project.group
%button.js-promote-project-milestone-button.btn.gl-button.btn-grouped{ data: { milestone_title: milestone.title,
group_name: milestone.project.group.name,
url: promote_project_milestone_path(milestone.project, milestone)},
disabled: true,
type: 'button' }
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-promote-project-milestone-button btn-grouped', data: { milestone_title: milestone.title, group_name: milestone.project.group.name, url: promote_project_milestone_path(milestone.project, milestone) }, disabled: true }) do
= _('Promote')
#promote-milestone-modal
- if milestone.active?
= link_to _('Close milestone'), update_milestone_path(milestone, { state_event: :close }), method: :put, class: 'btn gl-button btn-grouped btn-close'
= render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :close }), button_options: { class: 'btn-grouped btn-close', data: { method: 'put' }, rel: 'nofollow' }) do
= _('Close milestone')
- else
= link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn gl-button btn-grouped'
= render Pajamas::ButtonComponent.new(href: update_milestone_path(milestone, { state_event: :activate }), button_options: { class: 'btn-grouped', data: { method: 'put' }, rel: 'nofollow' }) do
= _('Reopen milestone')
= render 'shared/milestones/delete_button'
%button.btn.gl-button.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
= sprite_icon('chevron-double-lg-left')
= render Pajamas::ButtonComponent.new(icon: 'chevron-double-lg-left', button_options: { 'aria-label' => _('Toggle sidebar'), class: 'btn-grouped gl-float-right! gl-sm-display-none js-sidebar-toggle' })

View File

@ -1,4 +1,4 @@
= form_errors(hook)
= form_errors(hook, pajamas_alert: true)
.form-group
= form.label :url, s_('Webhooks|URL'), class: 'label-bold'

View File

@ -0,0 +1,8 @@
---
name: gitlab_sli_new_counters
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90977
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1760
milestone: '15.2'
type: development
group: group::scalability
default_enabled: false

View File

@ -0,0 +1,8 @@
---
table_name: post_migration_test_table
classes: []
feature_categories:
- database
description: Test table to verify the behavior of the post-deploy independent pipeline
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91119
milestone: '15.2'

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddPostMigrateTestTable < Gitlab::Database::Migration[2.0]
# Fake table to be used for testing the post-deploy pipeline,
# details can be seen on https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/2352.
#
# It should be deleted after the testing is completed.
def change
create_table :post_migration_test_table do |t|
t.integer :status, null: false
end
end
end

View File

@ -0,0 +1 @@
225606ccdf0979aaf70ff8b9a44269e69b1598718e3d7c1944ed41c07b5e33f6

View File

@ -18837,6 +18837,20 @@ CREATE SEQUENCE pool_repositories_id_seq
ALTER SEQUENCE pool_repositories_id_seq OWNED BY pool_repositories.id;
CREATE TABLE post_migration_test_table (
id bigint NOT NULL,
status integer NOT NULL
);
CREATE SEQUENCE post_migration_test_table_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE post_migration_test_table_id_seq OWNED BY post_migration_test_table.id;
CREATE TABLE postgres_async_indexes (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -23235,6 +23249,8 @@ ALTER TABLE ONLY plans ALTER COLUMN id SET DEFAULT nextval('plans_id_seq'::regcl
ALTER TABLE ONLY pool_repositories ALTER COLUMN id SET DEFAULT nextval('pool_repositories_id_seq'::regclass);
ALTER TABLE ONLY post_migration_test_table ALTER COLUMN id SET DEFAULT nextval('post_migration_test_table_id_seq'::regclass);
ALTER TABLE ONLY postgres_async_indexes ALTER COLUMN id SET DEFAULT nextval('postgres_async_indexes_id_seq'::regclass);
ALTER TABLE ONLY postgres_reindex_actions ALTER COLUMN id SET DEFAULT nextval('postgres_reindex_actions_id_seq'::regclass);
@ -25329,6 +25345,9 @@ ALTER TABLE ONLY plans
ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT pool_repositories_pkey PRIMARY KEY (id);
ALTER TABLE ONLY post_migration_test_table
ADD CONSTRAINT post_migration_test_table_pkey PRIMARY KEY (id);
ALTER TABLE ONLY postgres_async_indexes
ADD CONSTRAINT postgres_async_indexes_pkey PRIMARY KEY (id);

View File

@ -17,7 +17,7 @@ following levels are recognized:
- Reporter (`20`)
- Developer (`30`)
- Maintainer (`40`)
- Owner (`50`) - Only valid to set for groups
- Owner (`50`). Valid for projects in [GitLab 14.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/21432).
## List access requests for a group or project

View File

@ -20,7 +20,7 @@ levels are defined in the `Gitlab::Access` module. Currently, these levels are v
- Reporter (`20`)
- Developer (`30`)
- Maintainer (`40`)
- Owner (`50`) - Only valid to set for groups
- Owner (`50`). Valid for projects in [GitLab 14.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/21432).
NOTE:
From [GitLab 14.9](https://gitlab.com/gitlab-org/gitlab/-/issues/351211) and later, projects have a maximum role of Owner.

View File

@ -18,7 +18,7 @@ The access levels are defined in the `Gitlab::Access` module. Currently, these l
- Reporter (`20`)
- Developer (`30`)
- Maintainer (`40`)
- Owner (`50`) - Only valid to set for groups
- Owner (`50`). Valid for projects in [GitLab 14.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/21432).
NOTE:
In [GitLab 14.9](https://gitlab.com/gitlab-org/gitlab/-/issues/351211) and later, projects in personal namespaces have an `access_level` of `50`(Owner).

View File

@ -660,6 +660,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/yaml_optimizat
| `code-backstage-patterns` | Combination of `code-patterns` and `backstage-patterns`. |
| `code-qa-patterns` | Combination of `code-patterns` and `qa-patterns`. |
| `code-backstage-qa-patterns` | Combination of `code-patterns`, `backstage-patterns`, and `qa-patterns`. |
| `static-analysis-patterns` | Only create jobs for Static Analytics configuration-related changes. |
## Performance

View File

@ -628,8 +628,8 @@ You should consider some security implications before configuring IP address res
restricted IP address, the IP restriction prevents code from being cloned.
- Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include
push, merge, issue, or comment events.
- IP access restrictions for Git operations via SSH are supported only on **(PREMIUM SAAS)**
and higher tiers. IP access restrictions applied to self-managed instances block SSH completely.
- IP access restrictions for Git operations via SSH are supported only on GitLab SaaS.
IP access restrictions applied to self-managed instances block SSH completely.
### Restrict group access by IP address

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails/generators'
require 'rails/generators/active_record/model/model_generator'
module Model
class ModelGenerator < ActiveRecord::Generators::ModelGenerator
source_root File.expand_path('../../../generator_templates/active_record/migration/', __dir__)
def create_migration_file
return if skip_migration_creation?
if options[:indexes] == false
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? }
end
migration_template "create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")
end
# Override to find templates from superclass as well
def source_paths
super + [self.class.superclass.default_source_root]
end
end
end

View File

@ -390,6 +390,7 @@ personal_access_tokens: :gitlab_main
plan_limits: :gitlab_main
plans: :gitlab_main
pool_repositories: :gitlab_main
post_migration_test_table: :gitlab_main
postgres_async_indexes: :gitlab_shared
postgres_autovacuum_activity: :gitlab_shared
postgres_foreign_keys: :gitlab_shared

View File

@ -48,14 +48,24 @@ module Gitlab
# This module is effectively an abstract class
@initialized_with_combinations = possible_label_combinations.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
possible_label_combinations.each do |label_combination|
total_counter.get(label_combination)
numerator_counter.get(label_combination)
legacy_total_counter.get(label_combination)
legacy_numerator_counter.get(label_combination)
if ::Feature.enabled?(:gitlab_sli_new_counters)
total_counter.get(label_combination)
numerator_counter.get(label_combination)
end
end
end
def increment(labels:, increment_numerator:)
total_counter.increment(labels)
numerator_counter.increment(labels) if increment_numerator
legacy_total_counter.increment(labels)
legacy_numerator_counter.increment(labels) if increment_numerator
if ::Feature.enabled?(:gitlab_sli_new_counters)
total_counter.increment(labels)
numerator_counter.increment(labels) if increment_numerator
end
end
def initialized?
@ -65,7 +75,11 @@ module Gitlab
private
def total_counter
prometheus.counter(counter_name('total'), "Total number of measurements for #{name}")
prometheus.counter(counter_name('total', '_'), "Total number of measurements for #{name}")
end
def legacy_total_counter
prometheus.counter(counter_name('total', ':'), "Total number of measurements for #{name}")
end
def prometheus
@ -81,12 +95,16 @@ module Gitlab
private
def counter_name(suffix)
:"#{COUNTER_PREFIX}:#{name}_apdex:#{suffix}"
def counter_name(suffix, separator)
[COUNTER_PREFIX, "#{name}_apdex", suffix].join(separator).to_sym
end
def numerator_counter
prometheus.counter(counter_name('success_total'), "Number of successful measurements for #{name}")
prometheus.counter(counter_name('success_total', '_'), "Number of successful measurements for #{name}")
end
def legacy_numerator_counter
prometheus.counter(counter_name('success_total', ':'), "Legacy number of successful measurements for #{name}")
end
end
@ -99,12 +117,16 @@ module Gitlab
private
def counter_name(suffix)
:"#{COUNTER_PREFIX}:#{name}:#{suffix}"
def counter_name(suffix, separator)
[COUNTER_PREFIX, name, suffix].join(separator).to_sym
end
def numerator_counter
prometheus.counter(counter_name('error_total'), "Number of error measurements for #{name}")
prometheus.counter(counter_name('error_total', '_'), "Number of error measurements for #{name}")
end
def legacy_numerator_counter
prometheus.counter(counter_name('error_total', ':'), "Number of error measurements for #{name}")
end
end
end

View File

@ -30,7 +30,7 @@ module QA
fabricate!
end
QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, token)
QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, token) if @user
resource
end

View File

@ -10,6 +10,7 @@ module QA
end
def self.set_token_for_username(username, token)
QA::Runtime::Logger.info(%Q[Caching token for username: #{username}, last six chars of token:#{token[-6..]}])
@personal_access_tokens[username] = token
end
end

View File

@ -86,7 +86,10 @@ module QA
end
after do
@recreated_user&.remove_via_api!
if @recreated_user
@recreated_user.api_client = admin_api_client
@recreated_user.remove_via_api!
end
end
def admin_api_client

View File

@ -8,6 +8,7 @@ RSpec.describe Import::FogbugzController do
let(:user) { create(:user) }
let(:token) { FFaker::Lorem.characters(8) }
let(:uri) { 'https://example.com' }
let(:namespace_id) { 5 }
before do
sign_in(user)
@ -16,9 +17,11 @@ RSpec.describe Import::FogbugzController do
describe 'POST #callback' do
let(:xml_response) { %Q(<?xml version=\"1.0\" encoding=\"UTF-8\"?><response><token><![CDATA[#{token}]]></token></response>) }
it 'attempts to contact Fogbugz server' do
before do
stub_request(:post, "https://example.com/api.asp").to_return(status: 200, body: xml_response, headers: {})
end
it 'attempts to contact Fogbugz server' do
post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' }
expect(session[:fogbugz_token]).to eq(token)
@ -26,6 +29,20 @@ RSpec.describe Import::FogbugzController do
expect(response).to redirect_to(new_user_map_import_fogbugz_path)
end
it 'preserves namespace_id query param' do
post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword', namespace_id: namespace_id }
expect(response).to redirect_to(new_user_map_import_fogbugz_path(namespace_id: namespace_id))
end
it 'redirects to new page form when client raises authentication exception' do
allow(::Gitlab::FogbugzImport::Client).to receive(:new).and_raise(::Fogbugz::AuthenticationException)
post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' }
expect(response).to redirect_to(new_import_fogbugz_url)
end
context 'verify url' do
shared_examples 'denies local request' do |reason|
it 'does not allow requests' do
@ -76,6 +93,16 @@ RSpec.describe Import::FogbugzController do
expect(session[:fogbugz_user_map]).to eq(user_map)
expect(response).to redirect_to(status_import_fogbugz_path)
end
it 'preserves namespace_id query param' do
client = double(user_map: {})
expect(controller).to receive(:client).and_return(client)
post :create_user_map, params: { users: user_map, namespace_id: namespace_id }
expect(session[:fogbugz_user_map]).to eq(user_map)
expect(response).to redirect_to(status_import_fogbugz_path(namespace_id: namespace_id))
end
end
describe 'GET status' do
@ -84,11 +111,19 @@ RSpec.describe Import::FogbugzController do
id: 'demo', name: 'vim', safe_name: 'vim', path: 'vim')
end
before do
stub_client(valid?: true)
it 'redirects to new page form when client is invalid' do
stub_client(valid?: false)
get :status
expect(response).to redirect_to(new_import_fogbugz_path)
end
it_behaves_like 'import controller status' do
before do
stub_client(valid?: true)
end
let(:repo_id) { repo.id }
let(:import_source) { repo.name }
let(:provider_name) { 'fogbugz' }

View File

@ -1,214 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::LogsController do
include KubernetesHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:environment) do
create(:environment, name: 'production', project: project)
end
let(:pod_name) { "foo" }
let(:container) { 'container-1' }
before do
sign_in(user)
end
describe 'GET #index' do
let(:empty_project) { create(:project) }
it 'returns 404 with reporter access' do
project.add_reporter(user)
get :index, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
end
it 'renders empty logs page if no environment exists' do
empty_project.add_developer(user)
get :index, params: { namespace_id: empty_project.namespace, project_id: empty_project }
expect(response).to be_ok
expect(response).to render_template 'empty_logs'
end
it 'renders index template' do
project.add_developer(user)
get :index, params: environment_params
expect(response).to be_ok
expect(response).to render_template 'index'
end
context 'with feature flag disabled' do
before do
stub_feature_flags(monitor_logging: false)
end
it 'returns 404 with reporter access' do
project.add_developer(user)
get :index, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
shared_examples 'pod logs service' do |endpoint, service|
let(:service_result) do
{
status: :success,
logs: ['Log 1', 'Log 2', 'Log 3'],
pods: [pod_name],
pod_name: pod_name,
container_name: container
}
end
let(:service_result_json) { Gitlab::Json.parse(service_result.to_json) }
let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project]) }
before do
allow_next_instance_of(service) do |instance|
allow(instance).to receive(:execute).and_return(service_result)
end
end
it 'returns 404 with reporter access' do
project.add_reporter(user)
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with developer access' do
before do
project.add_developer(user)
end
it 'returns the service result' do
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:success)
expect(json_response).to eq(service_result_json)
end
end
context 'with maintainer access' do
before do
project.add_maintainer(user)
end
it 'returns the service result' do
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:success)
expect(json_response).to eq(service_result_json)
end
it 'sets the polling header' do
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:success)
expect(response.headers['Poll-Interval']).to eq('3000')
end
context 'with gitlab managed apps logs' do
it 'uses cluster finder services to select cluster', :aggregate_failures do
cluster_list = [cluster]
service_params = { params: ActionController::Parameters.new(pod_name: pod_name).permit! }
request_params = {
namespace_id: project.namespace,
project_id: project,
cluster_id: cluster.id,
pod_name: pod_name,
format: :json
}
expect_next_instance_of(ClusterAncestorsFinder, project, user) do |finder|
expect(finder).to receive(:execute).and_return(cluster_list)
expect(cluster_list).to receive(:find).and_call_original
end
expect_next_instance_of(service, cluster, Gitlab::Kubernetes::Helm::NAMESPACE, service_params) do |instance|
expect(instance).to receive(:execute).and_return(service_result)
end
get endpoint, params: request_params
expect(response).to have_gitlab_http_status(:success)
expect(json_response).to eq(service_result_json)
end
end
context 'when service is processing' do
let(:service_result) { nil }
it 'returns a 202' do
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:accepted)
end
end
shared_examples 'unsuccessful execution response' do |message|
let(:service_result) do
{
status: :error,
message: message
}
end
it 'returns the error' do
get endpoint, params: environment_params(pod_name: pod_name, format: :json)
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response).to eq(service_result_json)
end
end
context 'when service is failing' do
it_behaves_like 'unsuccessful execution response', 'some error'
end
context 'when cluster is nil' do
let!(:cluster) { nil }
it_behaves_like 'unsuccessful execution response', 'Environment does not have deployments'
end
context 'when namespace is empty' do
before do
allow(environment).to receive(:deployment_namespace).and_return('')
end
it_behaves_like 'unsuccessful execution response', 'Environment does not have deployments'
end
end
end
describe 'GET #k8s' do
it_behaves_like 'pod logs service', :k8s, PodLogs::KubernetesService
end
describe 'GET #elasticsearch' do
it_behaves_like 'pod logs service', :elasticsearch, PodLogs::ElasticsearchService
end
def environment_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace,
project_id: project,
environment_name: environment.name)
end
end

View File

@ -1,45 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Environment > Pod Logs', :js, :kubeclient do
include KubernetesHelpers
let(:pod_names) { %w(kube-pod) }
let(:pod_name) { pod_names.first }
let(:project) { create(:project, :repository) }
let(:environment) { create(:environment, project: project) }
let(:service) { create(:cluster_platform_kubernetes, :configured) }
before do
cluster = create(:cluster, :provided_by_gcp, environment_scope: '*', projects: [project])
create(:deployment, :success, environment: environment)
stub_kubeclient_pods(environment.deployment_namespace)
stub_kubeclient_deployments(environment.deployment_namespace)
stub_kubeclient_ingresses(environment.deployment_namespace)
stub_kubeclient_nodes_and_nodes_metrics(cluster.platform.api_url)
sign_in(project.first_owner)
end
it "shows environments in dropdown" do
create(:environment, project: project)
visit project_logs_path(environment.project, environment_name: environment.name, pod_name: pod_name)
wait_for_requests
page.within('.js-environments-dropdown') do
toggle = find(".dropdown-toggle:not([disabled])")
expect(toggle).to have_content(environment.name)
toggle.click
dropdown_items = find(".dropdown-menu").all(".dropdown-item")
expect(dropdown_items.first).to have_content(environment.name)
expect(dropdown_items.size).to eq(2)
end
end
end

View File

@ -1,4 +1,3 @@
import $ from 'jquery';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import NewBranchForm from '~/new_branch_form';
@ -11,17 +10,19 @@ describe('Branch', () => {
describe('create a new branch', () => {
function fillNameWith(value) {
$('.js-branch-name').val(value).trigger('blur');
document.querySelector('.js-branch-name').value = value;
const event = new CustomEvent('blur');
document.querySelector('.js-branch-name').dispatchEvent(event);
}
function expectToHaveError(error) {
expect($('.js-branch-name-error span').text()).toEqual(error);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual(error);
}
beforeEach(() => {
loadHTMLFixture('branches/new_branch.html');
$('form').on('submit', (e) => e.preventDefault());
testContext.form = new NewBranchForm($('.js-create-branch-form'), []);
document.querySelector('form').addEventListener('submit', (e) => e.preventDefault());
testContext.form = new NewBranchForm(document.querySelector('.js-create-branch-form'), []);
});
afterEach(() => {
@ -171,34 +172,34 @@ describe('Branch', () => {
it('removes the error message when is a valid name', () => {
fillNameWith('foo?bar');
expect($('.js-branch-name-error span').length).toEqual(1);
expect(document.querySelector('.js-branch-name-error').textContent).not.toEqual('');
fillNameWith('foobar');
expect($('.js-branch-name-error span').length).toEqual(0);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual('');
});
it('can have dashes anywhere', () => {
fillNameWith('-foo-bar-zoo-');
expect($('.js-branch-name-error span').length).toEqual(0);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual('');
});
it('can have underscores anywhere', () => {
fillNameWith('_foo_bar_zoo_');
expect($('.js-branch-name-error span').length).toEqual(0);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual('');
});
it('can have numbers anywhere', () => {
fillNameWith('1foo2bar3zoo4');
expect($('.js-branch-name-error span').length).toEqual(0);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual('');
});
it('can be only letters', () => {
fillNameWith('foo');
expect($('.js-branch-name-error span').length).toEqual(0);
expect(document.querySelector('.js-branch-name-error').textContent).toEqual('');
});
});
});

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateModelGeneratorTestFoos < Gitlab::Database::Migration[2.0]
# When using the methods "add_concurrent_index" or "remove_concurrent_index"
# you must disable the use of transactions
# as these methods can not run in an existing transaction.
# When using "add_concurrent_index" or "remove_concurrent_index" methods make sure
# that either of them is the _only_ method called in the migration,
# any other changes should go in a separate migration.
# This ensures that upon failure _only_ the index creation or removing fails
# and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def change
create_table :model_generator_test_foos do |t|
t.timestamps null: false
end
end
end

View File

@ -0,0 +1,2 @@
class ModelGeneratorTestFoo < ApplicationRecord
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe ModelGeneratorTestFoo, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Model::ModelGenerator do
let(:args) { ['ModelGeneratorTestFoo'] }
let(:options) { { 'migration' => true, 'timestamps' => true, 'indexes' => true, 'test_framework' => :rspec } }
let(:temp_dir) { Dir.mktmpdir }
let(:migration_file_path) { Dir.glob(File.join(temp_dir, 'db/migrate/*create_model_generator_test_foos.rb')).first }
let(:model_file_path) { File.join(temp_dir, 'app/models/model_generator_test_foo.rb') }
let(:spec_file_path) { File.join(temp_dir, 'spec/models/model_generator_test_foo_spec.rb') }
subject { described_class.new(args, options, { destination_root: temp_dir }) }
context 'when generating a model' do
after do
FileUtils.rm_rf(temp_dir)
end
it 'creates the model file with the right content' do
subject.invoke_all
expect(File).to exist(model_file_path)
mock_model_file_content = File.read(File.expand_path('./mocks/model_file.txt', __dir__))
model_file_content = File.read(model_file_path)
expect(model_file_content).to eq(mock_model_file_content)
end
it 'creates the migration file with the right content' do
subject.invoke_all
expect(File).to exist(migration_file_path)
mock_migration_file_content = File.read(File.expand_path('./mocks/migration_file.txt', __dir__))
migration_file_content = File.read(migration_file_path)
expect(migration_file_content).to eq(mock_migration_file_content)
end
it 'creates the spec file with the right content' do
subject.invoke_all
expect(File).to exist(spec_file_path)
mock_spec_file_content = File.read(File.expand_path('./mocks/spec_file.txt', __dir__))
spec_file_content = File.read(spec_file_path)
expect(spec_file_content).to eq(mock_spec_file_content)
end
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
require 'fast_spec_helper'
# Switch this back to `fast_spec_helper` when removing the `gitlab_sli_new_counters`
# feature flag
require 'spec_helper'
RSpec.describe Gitlab::Metrics::Sli do
let(:prometheus) { double("prometheus") }
@ -18,12 +20,16 @@ RSpec.describe Gitlab::Metrics::Sli do
it 'allows different SLIs to be defined on each subclass' do
apdex_counters = [
fake_total_counter('foo_apdex'),
fake_numerator_counter('foo_apdex', 'success')
fake_numerator_counter('foo_apdex', 'success'),
fake_total_counter('foo_apdex', ':'),
fake_numerator_counter('foo_apdex', 'success', ':')
]
error_rate_counters = [
fake_total_counter('foo'),
fake_numerator_counter('foo', 'error')
fake_numerator_counter('foo', 'error'),
fake_total_counter('foo', ':'),
fake_numerator_counter('foo', 'error', ':')
]
apdex = described_class::Apdex.initialize_sli(:foo, [{ hello: :world }])
@ -78,7 +84,9 @@ RSpec.describe Gitlab::Metrics::Sli do
it 'returns and stores a new initialized SLI' do
counters = [
fake_total_counter("bar#{subclass_info[:suffix]}"),
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator]),
fake_total_counter("bar#{subclass_info[:suffix]}", ':'),
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
]
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
@ -91,7 +99,9 @@ RSpec.describe Gitlab::Metrics::Sli do
it 'does not change labels for an already-initialized SLI' do
counters = [
fake_total_counter("bar#{subclass_info[:suffix]}"),
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator]),
fake_total_counter("bar#{subclass_info[:suffix]}", ':'),
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
]
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
@ -112,6 +122,8 @@ RSpec.describe Gitlab::Metrics::Sli do
before do
fake_total_counter("boom#{subclass_info[:suffix]}")
fake_numerator_counter("boom#{subclass_info[:suffix]}", subclass_info[:numerator])
fake_total_counter("boom#{subclass_info[:suffix]}", ':')
fake_numerator_counter("boom#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
end
it 'is true when an SLI was initialized with labels' do
@ -130,7 +142,9 @@ RSpec.describe Gitlab::Metrics::Sli do
it 'initializes counters for the passed label combinations' do
counters = [
fake_total_counter("hey#{subclass_info[:suffix]}"),
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator])
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator]),
fake_total_counter("hey#{subclass_info[:suffix]}", ':'),
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
]
described_class.new(:hey).initialize_counters([{ foo: 'bar' }, { foo: 'baz' }])
@ -138,18 +152,43 @@ RSpec.describe Gitlab::Metrics::Sli do
expect(counters).to all(have_received(:get).with({ foo: 'bar' }))
expect(counters).to all(have_received(:get).with({ foo: 'baz' }))
end
context 'when `gitlab_sli_new_counters` is disabled' do
before do
stub_feature_flags(gitlab_sli_new_counters: false)
end
it 'does not initialize the new counters', :aggregate_failures do
new_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}")
new_numerator_counter = fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
fake_total_counter("bar#{subclass_info[:suffix]}", ':')
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
described_class.new(:bar).initialize_counters([{ hello: :world }])
expect(new_total_counter).not_to have_received(:get)
expect(new_numerator_counter).not_to have_received(:get)
end
end
end
describe "#increment" do
let!(:sli) { described_class.new(:heyo) }
let!(:total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}") }
let!(:numerator_counter) { fake_numerator_counter("heyo#{subclass_info[:suffix]}", subclass_info[:numerator]) }
let!(:legacy_total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}", ':') }
let!(:legacy_numerator_counter) do
fake_numerator_counter("heyo#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
end
it "increments both counters for labels when #{subclass_info[:numerator]} is true" do
sli.increment(labels: { hello: "world" }, subclass_info[:numerator] => true)
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
expect(numerator_counter).to have_received(:increment).with({ hello: 'world' })
expect(legacy_total_counter).to have_received(:increment).with({ hello: 'world' })
expect(legacy_numerator_counter).to have_received(:increment).with({ hello: 'world' })
end
it "only increments the total counters for labels when #{subclass_info[:numerator]} is false" do
@ -157,6 +196,31 @@ RSpec.describe Gitlab::Metrics::Sli do
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
expect(numerator_counter).not_to have_received(:increment).with({ hello: 'world' })
expect(legacy_total_counter).to have_received(:increment).with({ hello: 'world' })
expect(legacy_numerator_counter).not_to have_received(:increment).with({ hello: 'world' })
end
context 'when `gitlab_sli_new_counters` is disabled' do
before do
stub_feature_flags(gitlab_sli_new_counters: false)
end
it 'does increment new counters', :aggregate_failures do
new_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}")
new_numerator_counter = fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
legacy_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}", ':')
legacy_numerator_counter = fake_numerator_counter(
"bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':'
)
described_class.new(:bar).increment(labels: { hello: 'world' }, subclass_info[:numerator] => true)
expect(new_total_counter).not_to have_received(:increment)
expect(new_numerator_counter).not_to have_received(:increment)
expect(legacy_total_counter).to have_received(:increment)
expect(legacy_numerator_counter).to have_received(:increment)
end
end
end
end
@ -172,11 +236,11 @@ RSpec.describe Gitlab::Metrics::Sli do
fake_counter
end
def fake_total_counter(name)
fake_prometheus_counter("gitlab_sli:#{name}:total")
def fake_total_counter(name, separator = '_')
fake_prometheus_counter(['gitlab_sli', name, 'total'].join(separator))
end
def fake_numerator_counter(name, numerator_name)
fake_prometheus_counter("gitlab_sli:#{name}:#{numerator_name}_total")
def fake_numerator_counter(name, numerator_name, separator = '_')
fake_prometheus_counter(["gitlab_sli", name, "#{numerator_name}_total"].join(separator))
end
end

View File

@ -70,10 +70,4 @@ RSpec.describe Ci::BuildReportResult do
expect(build_report_result.tests_skipped).to eq(0)
end
end
describe '#tests_total' do
it 'returns the total count' do
expect(build_report_result.tests_total).to eq(2)
end
end
end