Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-09 00:08:42 +00:00
parent 459939f27f
commit f0809fbc2c
39 changed files with 487 additions and 241 deletions

View File

@ -341,7 +341,6 @@ group :development do
# Better errors handler
gem 'better_errors', '~> 2.7.1'
gem 'binding_of_caller', '~> 0.8.0'
# thin instead webrick
gem 'thin', '~> 1.7.0'

View File

@ -135,8 +135,6 @@ GEM
rack (>= 0.9.0)
bindata (2.4.3)
binding_ninja (0.2.3)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
bootsnap (1.4.6)
msgpack (~> 1.0)
bootstrap_form (4.2.0)
@ -218,7 +216,6 @@ GEM
octokit (~> 4.7)
terminal-table (~> 1)
database_cleaner (1.7.0)
debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8)
deckar01-task_list (2.3.1)
html-pipeline
@ -1263,7 +1260,6 @@ DEPENDENCIES
benchmark-ips (~> 2.3.0)
benchmark-memory (~> 0.1)
better_errors (~> 2.7.1)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4.6)
bootstrap_form (~> 4.2.0)
brakeman (~> 4.2)

View File

@ -0,0 +1,68 @@
/* Wait for.... The methods can be used:
- with a callback (preferred),
waitFor(action)
- with then (discouraged),
await waitFor().then(action);
- with await,
await waitFor;
action();
*/
const CSS_LOADED_EVENT = 'CSSLoaded';
const DOM_LOADED_EVENT = 'DOMContentLoaded';
const STARTUP_LINK_LOADED_EVENT = 'CSSStartupLinkLoaded';
const isStartupLinkLoaded = ({ dataset }) => dataset.startupcss === 'loaded';
export const handleLoadedEvents = (action = () => {}) => {
let isCssLoaded = false;
let eventsList = [CSS_LOADED_EVENT, DOM_LOADED_EVENT];
return ({ type } = {}) => {
eventsList = eventsList.filter(e => e !== type);
if (isCssLoaded) {
return;
}
if (!eventsList.length) {
isCssLoaded = true;
action();
}
};
};
export const handleStartupEvents = (action = () => {}) => {
if (!gon.features.startupCss) {
return action;
}
const startupLinks = Array.from(document.querySelectorAll('link[data-startupcss]'));
return () => {
if (startupLinks.every(isStartupLinkLoaded)) {
action();
}
};
};
export const waitForStartupLinks = () => {
let eventListener;
const promise = new Promise(resolve => {
eventListener = handleStartupEvents(resolve);
document.addEventListener(STARTUP_LINK_LOADED_EVENT, eventListener);
}).then(() => {
document.dispatchEvent(new CustomEvent(CSS_LOADED_EVENT));
document.removeEventListener(STARTUP_LINK_LOADED_EVENT, eventListener);
});
document.dispatchEvent(new CustomEvent(STARTUP_LINK_LOADED_EVENT));
return promise;
};
export const waitForCSSLoaded = (action = () => {}) => {
let eventListener;
const promise = new Promise(resolve => {
eventListener = handleLoadedEvents(resolve);
document.addEventListener(DOM_LOADED_EVENT, eventListener, { once: true });
document.addEventListener(CSS_LOADED_EVENT, eventListener, { once: true });
}).then(action);
waitForStartupLinks();
return promise;
};

View File

@ -1,18 +1,17 @@
import Vue from 'vue';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
import { waitForCSSLoaded } from '../../../../helpers/startup_css_helper';
import { __ } from '~/locale';
import CodeCoverage from '../components/code_coverage.vue';
import SeriesDataMixin from './series_data_mixin';
document.addEventListener('DOMContentLoaded', () => {
waitForCSSLoaded(() => {
const languagesContainer = document.getElementById('js-languages-chart');
const codeCoverageContainer = document.getElementById('js-code-coverage-chart');
const monthContainer = document.getElementById('js-month-chart');
const weekdayContainer = document.getElementById('js-weekday-chart');
const hourContainer = document.getElementById('js-hour-chart');
const LANGUAGE_CHART_HEIGHT = 300;
const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
if (firstDayOfWeek === 0) {
return weekDays;

View File

@ -22,7 +22,7 @@ module AuthenticatesWithTwoFactor
return handle_locked_user(user) unless user.can?(:log_in)
session[:otp_user_id] = user.id
session[:user_updated_at] = user.updated_at
session[:user_password_hash] = Digest::SHA256.hexdigest(user.encrypted_password)
push_frontend_feature_flag(:webauthn)
if user.two_factor_webauthn_enabled?
@ -47,7 +47,7 @@ module AuthenticatesWithTwoFactor
def authenticate_with_two_factor
user = self.resource = find_user
return handle_locked_user(user) unless user.can?(:log_in)
return handle_changed_user(user) if user_changed?(user)
return handle_changed_user(user) if user_password_changed?(user)
if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
@ -76,7 +76,7 @@ module AuthenticatesWithTwoFactor
def clear_two_factor_attempt!
session.delete(:otp_user_id)
session.delete(:user_updated_at)
session.delete(:user_password_hash)
session.delete(:challenge)
end
@ -142,7 +142,6 @@ module AuthenticatesWithTwoFactor
gon.push(webauthn: { options: get_options.to_json })
end
end
# rubocop: enable CodeReuse/ActiveRecord
def handle_two_factor_success(user)
@ -168,13 +167,9 @@ module AuthenticatesWithTwoFactor
# If user has been updated since we validated the password,
# the password might have changed.
def user_changed?(user)
return false unless session[:user_updated_at]
def user_password_changed?(user)
return false unless session[:user_password_hash]
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/244638
# Rounding errors happen when the user is updated, as the Rails ActiveRecord
# object has higher precision than what is stored in the database, therefore
# using .to_i to force truncation to the timestamp
user.updated_at.to_i != session[:user_updated_at].to_i
Digest::SHA256.hexdigest(user.encrypted_password) != session[:user_password_hash]
end
end

View File

@ -4,6 +4,8 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
include StartupCssHelper
# See https://docs.gitlab.com/ee/development/ee_features.html#code-in-app-views
# rubocop: disable CodeReuse/ActiveRecord
def render_if_exists(partial, locals = {})
@ -235,10 +237,6 @@ module ApplicationHelper
"#{request.path}?#{options.compact.to_param}"
end
def use_startup_css?
(Feature.enabled?(:startup_css) || params[:startup_css] == 'true' || cookies['startup_css'] == 'true') && !Rails.env.test?
end
def stylesheet_link_tag_defer(path)
if use_startup_css?
stylesheet_link_tag(path, media: "print", crossorigin: ActionController::Base.asset_host ? 'anonymous' : nil)

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module StartupCssHelper
def use_startup_css?
(Feature.enabled?(:startup_css) || params[:startup_css] == 'true' || cookies['startup_css'] == 'true') && !Rails.env.test?
end
end

View File

@ -203,7 +203,7 @@ class CommitStatus < ApplicationRecord
common_name = name.to_s.gsub(%r{\d+[\s:\/\\]+\d+\s*}, '')
# 'rspec:linux: [aws, max memory]' => 'rspec:linux'
common_name.gsub!(%r{: \[.*, .*\]\s*\z}, '') if Gitlab::Ci::Features.new_matrix_job_names_enabled?
common_name.gsub!(%r{: \[.*, .*\]\s*\z}, '')
common_name.strip!
common_name

View File

@ -896,12 +896,12 @@ class Project < ApplicationRecord
end
def repository
@repository ||= Repository.new(full_path, self, shard: repository_storage, disk_path: disk_path)
@repository ||= Gitlab::GlRepository::PROJECT.repository_for(self)
end
def design_repository
strong_memoize(:design_repository) do
DesignManagement::Repository.new(self)
Gitlab::GlRepository::DESIGN.repository_for(self)
end
end

View File

@ -275,7 +275,7 @@ class Snippet < ApplicationRecord
override :repository
def repository
@repository ||= Repository.new(full_path, self, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::SNIPPET)
@repository ||= Gitlab::GlRepository::SNIPPET.repository_for(self)
end
override :repository_size_checker

View File

@ -180,7 +180,7 @@ class Wiki
override :repository
def repository
@repository ||= Repository.new(full_path, container, shard: repository_storage, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
@repository ||= Gitlab::GlRepository::WIKI.repository_for(container)
end
def repository_storage

View File

@ -3,5 +3,8 @@
= javascript_tag nonce: true do
:plain
document.querySelectorAll('link[media="print"]').forEach(linkTag => {
linkTag.addEventListener('load', function() {this.media='all'}, {once: true});
linkTag.setAttribute('data-startupcss', 'loading');
const startupLinkLoadedEvent = new CustomEvent('CSSStartupLinkLoaded');
linkTag.addEventListener('load',function(){this.media='all';this.setAttribute('data-startupcss', 'loaded');document.dispatchEvent(startupLinkLoadedEvent);},{once: true});
})
- return unless use_startup_css?

View File

@ -0,0 +1,5 @@
---
title: Add target_id column to audit_events table
merge_request: 40954
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Initialise charts when container display property is set
merge_request: 40787
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Change 2FA to verify password hash instead of timestamp
merge_request: 41366
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Convert spec_helper to fast_spec_helper
merge_request: 41755
author: gaga5lala
type: other

View File

@ -0,0 +1,5 @@
---
title: Parallel matrix jobs show relevant variables in job name
merge_request: 41080
author:
type: added

View File

@ -1,7 +0,0 @@
---
name: ci_matrix_job_names
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39985
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/239012
group: 'group::continuous integration'
type: development
default_enabled: false

View File

@ -0,0 +1,125 @@
# frozen_string_literal: true
class AddTargetIdToAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::SchemaHelpers
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
SOURCE_TABLE_NAME = 'audit_events'
PARTITIONED_TABLE_NAME = 'audit_events_part_5fc467ac26'
TRIGGER_FUNCTION_NAME = 'table_sync_function_2be879775d'
def up
with_lock_retries do
add_column(SOURCE_TABLE_NAME, :target_id, :bigint)
add_column(PARTITIONED_TABLE_NAME, :target_id, :bigint)
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
target_id = NEW.target_id,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
type,
entity_id,
entity_type,
details,
ip_address,
author_name,
entity_path,
target_details,
target_type,
target_id,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.target_id,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
end
end
def down
with_lock_retries do
remove_column SOURCE_TABLE_NAME, :target_id
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
type,
entity_id,
entity_type,
details,
ip_address,
author_name,
entity_path,
target_details,
target_type,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
remove_column PARTITIONED_TABLE_NAME, :target_id
end
end
end

View File

@ -0,0 +1 @@
19c90689d0af6adb017dbd7127c6cd147d9c92581118dbfd99c87bc6a6dda3be

View File

@ -30,6 +30,7 @@ ELSIF (TG_OP = 'UPDATE') THEN
entity_path = NEW.entity_path,
target_details = NEW.target_details,
target_type = NEW.target_type,
target_id = NEW.target_id,
created_at = NEW.created_at
WHERE audit_events_part_5fc467ac26.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
@ -44,6 +45,7 @@ ELSIF (TG_OP = 'INSERT') THEN
entity_path,
target_details,
target_type,
target_id,
created_at)
VALUES (NEW.id,
NEW.author_id,
@ -56,6 +58,7 @@ ELSIF (TG_OP = 'INSERT') THEN
NEW.entity_path,
NEW.target_details,
NEW.target_type,
NEW.target_id,
NEW.created_at);
END IF;
RETURN NULL;
@ -78,6 +81,7 @@ CREATE TABLE public.audit_events_part_5fc467ac26 (
target_details text,
created_at timestamp without time zone NOT NULL,
target_type text,
target_id bigint,
CONSTRAINT check_492aaa021d CHECK ((char_length(entity_path) <= 5500)),
CONSTRAINT check_83ff8406e2 CHECK ((char_length(author_name) <= 255)),
CONSTRAINT check_97a8c868e7 CHECK ((char_length(target_type) <= 255)),
@ -9518,6 +9522,7 @@ CREATE TABLE public.audit_events (
entity_path text,
target_details text,
target_type text,
target_id bigint,
CONSTRAINT check_492aaa021d CHECK ((char_length(entity_path) <= 5500)),
CONSTRAINT check_82294106dd CHECK ((char_length(target_type) <= 255)),
CONSTRAINT check_83ff8406e2 CHECK ((char_length(author_name) <= 255)),

View File

@ -1,6 +1,7 @@
---
stage: Manage
group: Compliance
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
description: 'Learn how to create evidence artifacts typically requested by a 3rd party auditor.'
---

View File

@ -3594,18 +3594,20 @@ deploystacks:
This generates 10 parallel `deploystacks` jobs, each with different values for `PROVIDER` and `STACK`:
```plaintext
deploystacks 1/10 with PROVIDER=aws and STACK=monitoring
deploystacks 2/10 with PROVIDER=aws and STACK=app1
deploystacks 3/10 with PROVIDER=aws and STACK=app2
deploystacks 4/10 with PROVIDER=ovh and STACK=monitoring
deploystacks 5/10 with PROVIDER=ovh and STACK=backup
deploystacks 6/10 with PROVIDER=ovh and STACK=app
deploystacks 7/10 with PROVIDER=gcp and STACK=data
deploystacks 8/10 with PROVIDER=gcp and STACK=processing
deploystacks 9/10 with PROVIDER=vultr and STACK=data
deploystacks 10/10 with PROVIDER=vultr and STACK=processing
deploystacks: [aws, monitoring]
deploystacks: [aws, app1]
deploystacks: [aws, app2]
deploystacks: [ovh, monitoring]
deploystacks: [ovh, backup]
deploystacks: [ovh, app]
deploystacks: [gcp, data]
deploystacks: [gcp, processing]
deploystacks: [vultr, data]
deploystacks: [vultr, processing]
```
Job naming style [was improved](https://gitlab.com/gitlab-org/gitlab/-/issues/230452) in GitLab 13.4.
### `trigger`
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.

View File

@ -265,10 +265,16 @@ This documentation gives an overview of the report JSON format,
as well as recommendations and examples to help integrators set its fields.
The format is extensively described in the documentation of
[SAST](../../user/application_security/sast/index.md#reports-json-format),
[DAST](../../user/application_security/dast/#reports),
[Dependency Scanning](../../user/application_security/dependency_scanning/index.md#reports-json-format),
and [Container Scanning](../../user/application_security/container_scanning/index.md#reports-json-format).
The DAST variant of the report JSON format is not documented at the moment.
You can find the schemas for these scanners here:
- [SAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json)
- [DAST](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dast-report-format.json)
- [Dependency Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/dependency-scanning-report-format.json)
- [Container Scanning](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/container-scanning-report-format.json)
### Version

View File

@ -148,7 +148,7 @@ module Backup
private
def dump_consecutive
Project.includes(:route).find_each(batch_size: 1000) do |project|
Project.includes(:route, :group, namespace: :owner).find_each(batch_size: 1000) do |project|
dump_project(project)
end
end
@ -178,7 +178,7 @@ module Backup
end
end
Project.for_repository_storage(storage).includes(:route).find_each(batch_size: 100) do |project|
Project.for_repository_storage(storage).includes(:route, :group, namespace: :owner).find_each(batch_size: 100) do |project|
break unless errors.empty?
queue.push(project)

View File

@ -49,16 +49,6 @@ module Gitlab
end
def name
if Gitlab::Ci::Features.new_matrix_job_names_enabled?
name_with_variable_details
else
old_name
end
end
private
def name_with_variable_details
vars = variables
.values
.compact
@ -67,9 +57,7 @@ module Gitlab
"#{job_name}: [#{vars}]"
end
def old_name
"#{job_name} #{instance}/#{total}"
end
private
attr_reader :job_name, :instance, :variables, :total
end

View File

@ -67,10 +67,6 @@ module Gitlab
Feature.enabled?(:project_transactionless_destroy, project, default_enabled: false)
end
def self.new_matrix_job_names_enabled?
::Feature.enabled?(:ci_matrix_job_names, default_enabled: false)
end
def self.coverage_report_view?(project)
::Feature.enabled?(:coverage_report_view, project)
end

View File

@ -7,19 +7,19 @@ module Gitlab
PROJECT = RepoType.new(
name: :project,
access_checker_class: Gitlab::GitAccessProject,
repository_resolver: -> (project) { project&.repository }
repository_resolver: -> (project) { ::Repository.new(project.full_path, project, shard: project.repository_storage, disk_path: project.disk_path) }
).freeze
WIKI = RepoType.new(
name: :wiki,
access_checker_class: Gitlab::GitAccessWiki,
repository_resolver: -> (container) { container&.wiki&.repository },
repository_resolver: -> (container) { ::Repository.new(container.wiki.full_path, container, shard: container.wiki.repository_storage, disk_path: container.wiki.disk_path, repo_type: WIKI) },
project_resolver: -> (container) { container.is_a?(Project) ? container : nil },
suffix: :wiki
).freeze
SNIPPET = RepoType.new(
name: :snippet,
access_checker_class: Gitlab::GitAccessSnippet,
repository_resolver: -> (snippet) { snippet&.repository },
repository_resolver: -> (snippet) { ::Repository.new(snippet.full_path, snippet, shard: snippet.repository_storage, disk_path: snippet.disk_path, repo_type: SNIPPET) },
container_class: Snippet,
project_resolver: -> (snippet) { snippet&.project },
guest_read_ability: :read_snippet

View File

@ -57,6 +57,8 @@ module Gitlab
end
def repository_for(container)
return unless container
repository_resolver.call(container)
end

View File

@ -4,6 +4,7 @@
module Gitlab
module GonHelper
include StartupCssHelper
include WebpackHelper
def add_gon_variables
@ -48,6 +49,9 @@ module Gitlab
push_frontend_feature_flag(:snippets_edit_vue, default_enabled: false)
push_frontend_feature_flag(:webperf_experiment, default_enabled: false)
push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false)
# Startup CSS feature is a special one as it can be enabled by means of cookies and params
gon.push({ features: { 'startupCss' => use_startup_css? } }, true)
end
# Exposes the state of a feature flag to the frontend code.

View File

@ -18,8 +18,8 @@ RSpec.describe 'Database schema' do
approvals: %w[user_id],
approver_groups: %w[target_id],
approvers: %w[target_id user_id],
audit_events: %w[author_id entity_id],
audit_events_part_5fc467ac26: %w[author_id entity_id],
audit_events: %w[author_id entity_id target_id],
audit_events_part_5fc467ac26: %w[author_id entity_id target_id],
award_emoji: %w[awardable_id user_id],
aws_roles: %w[role_external_id],
boards: %w[milestone_id],

View File

@ -0,0 +1,123 @@
import {
handleLoadedEvents,
waitForCSSLoaded,
} from '../../../app/assets/javascripts/helpers/startup_css_helper';
describe('handleLoadedEvents', () => {
let mock;
beforeEach(() => {
mock = jest.fn();
});
it('should not call the callback on wrong conditions', () => {
const resolverToCall = handleLoadedEvents(mock);
resolverToCall({ type: 'UnrelatedEvent' });
resolverToCall({ type: 'UnrelatedEvent' });
resolverToCall({ type: 'UnrelatedEvent' });
resolverToCall({ type: 'UnrelatedEvent' });
resolverToCall({ type: 'CSSLoaded' });
resolverToCall();
expect(mock).not.toHaveBeenCalled();
});
it('should call the callback when all the events have been triggered', () => {
const resolverToCall = handleLoadedEvents(mock);
resolverToCall();
resolverToCall({ type: 'DOMContentLoaded' });
resolverToCall({ type: 'CSSLoaded' });
resolverToCall();
expect(mock).toHaveBeenCalledTimes(1);
});
});
describe('waitForCSSLoaded', () => {
let mock;
beforeEach(() => {
mock = jest.fn();
});
describe('with startup css disabled', () => {
beforeEach(() => {
gon.features = {
startupCss: false,
};
});
it('should call CssLoaded when the conditions are met', async () => {
const docAddListener = jest.spyOn(document, 'addEventListener');
const docRemoveListener = jest.spyOn(document, 'removeEventListener');
const docDispatch = jest.spyOn(document, 'dispatchEvent');
const events = waitForCSSLoaded(mock);
expect(docAddListener).toHaveBeenCalledTimes(3);
expect(docDispatch.mock.calls[0][0].type).toBe('CSSStartupLinkLoaded');
document.dispatchEvent(new CustomEvent('DOMContentLoaded'));
await events;
expect(docDispatch).toHaveBeenCalledTimes(3);
expect(docDispatch.mock.calls[2][0].type).toBe('CSSLoaded');
expect(docRemoveListener).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledTimes(1);
});
});
describe('with startup css enabled', () => {
let docAddListener;
let docRemoveListener;
let docDispatch;
beforeEach(() => {
docAddListener = jest.spyOn(document, 'addEventListener');
docRemoveListener = jest.spyOn(document, 'removeEventListener');
docDispatch = jest.spyOn(document, 'dispatchEvent');
gon.features = {
startupCss: true,
};
});
it('should call CssLoaded if the assets are cached', async () => {
const events = waitForCSSLoaded(mock);
const fixtures = `
<link href="one.css" data-startupcss="loaded">
<link href="two.css" data-startupcss="loaded">
`;
setFixtures(fixtures);
expect(docAddListener).toHaveBeenCalledTimes(3);
expect(docDispatch.mock.calls[0][0].type).toBe('CSSStartupLinkLoaded');
document.dispatchEvent(new CustomEvent('DOMContentLoaded'));
await events;
expect(docDispatch).toHaveBeenCalledTimes(3);
expect(docDispatch.mock.calls[2][0].type).toBe('CSSLoaded');
expect(docRemoveListener).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledTimes(1);
});
it('should wait to call CssLoaded until the assets are loaded', async () => {
const events = waitForCSSLoaded(mock);
const fixtures = `
<link href="one.css" data-startupcss="loading">
<link href="two.css" data-startupcss="loading">
`;
setFixtures(fixtures);
expect(docAddListener).toHaveBeenCalledTimes(3);
expect(docDispatch.mock.calls[0][0].type).toBe('CSSStartupLinkLoaded');
document
.querySelectorAll('[data-startupcss="loading"]')
.forEach(elem => elem.setAttribute('data-startupcss', 'loaded'));
document.dispatchEvent(new CustomEvent('DOMContentLoaded'));
document.dispatchEvent(new CustomEvent('CSSStartupLinkLoaded'));
await events;
expect(docDispatch).toHaveBeenCalledTimes(4);
expect(docDispatch.mock.calls[3][0].type).toBe('CSSLoaded');
expect(docRemoveListener).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
describe '.applies_to?' do
@ -39,114 +39,53 @@ RSpec.describe Gitlab::Ci::Config::Normalizer::MatrixStrategy do
it { expect(subject.size).to eq(4) }
context 'with new_matrix_job_names_enabled ff disabled' do
before do
stub_feature_flags(ci_matrix_job_names: false)
end
it 'has attributes' do
expect(subject.map(&:attributes)).to match_array(
[
{
name: 'test 1/4',
instance: 1,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
}
},
{
name: 'test 2/4',
instance: 2,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
}
},
{
name: 'test 3/4',
instance: 3,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
}
},
{
name: 'test 4/4',
instance: 4,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
}
it 'has attributes' do
expect(subject.map(&:attributes)).to match_array(
[
{
name: 'test: [aws, app1]',
instance: 1,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
}
]
)
end
it 'has parallelized name' do
expect(subject.map(&:name)).to match_array(
['test 1/4', 'test 2/4', 'test 3/4', 'test 4/4']
)
end
},
{
name: 'test: [aws, app2]',
instance: 2,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
}
},
{
name: 'test: [ovh, app]',
instance: 3,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
}
},
{
name: 'test: [gcp, app]',
instance: 4,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
}
}
]
)
end
context 'with new_matrix_job_names_enabled ff enabled' do
before do
stub_feature_flags(ci_matrix_job_names: true)
end
it 'has attributes' do
expect(subject.map(&:attributes)).to match_array(
[
{
name: 'test: [aws, app1]',
instance: 1,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app1'
}
},
{
name: 'test: [aws, app2]',
instance: 2,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'aws',
'STACK' => 'app2'
}
},
{
name: 'test: [ovh, app]',
instance: 3,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'ovh',
'STACK' => 'app'
}
},
{
name: 'test: [gcp, app]',
instance: 4,
parallel: { total: 4 },
variables: {
'PROVIDER' => 'gcp',
'STACK' => 'app'
}
}
]
)
end
it 'has parallelized name' do
expect(subject.map(&:name)).to match_array(
['test: [aws, app1]', 'test: [aws, app2]', 'test: [gcp, app]', 'test: [ovh, app]']
)
end
it 'has parallelized name' do
expect(subject.map(&:name)).to match_array(
['test: [aws, app1]', 'test: [aws, app2]', 'test: [gcp, app]', 'test: [ovh, app]']
)
end
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Normalizer do
let(:job_name) { :rspec }
@ -185,6 +185,13 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
}
end
let(:expanded_job_names) do
[
'rspec: [A, B]',
'rspec: [A, C]'
]
end
it 'does not have original job' do
is_expected.not_to include(job_name)
end
@ -215,45 +222,12 @@ RSpec.describe Gitlab::Ci::Config::Normalizer do
expect(configs).to all(match(a_hash_including(original_config)))
end
context 'with new_matrix_job_names_enabled ff enabled' do
let(:expanded_job_names) do
[
'rspec: [A, B]',
'rspec: [A, C]'
]
end
before do
stub_feature_flags(ci_matrix_job_names: true)
end
it 'has parallelized jobs' do
is_expected.to include(*expanded_job_names.map(&:to_sym))
end
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
it 'has parallelized jobs' do
is_expected.to include(*expanded_job_names.map(&:to_sym))
end
context 'with new_matrix_job_names_enabled ff disabled' do
let(:expanded_job_names) do
[
'rspec 1/2',
'rspec 2/2'
]
end
before do
stub_feature_flags(ci_matrix_job_names: false)
end
it 'has parallelized jobs' do
is_expected.to include(*expanded_job_names.map(&:to_sym))
end
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
end
it_behaves_like 'parallel dependencies'
it_behaves_like 'parallel needs'
end
context 'when parallel config does not matches a factory' do

View File

@ -17,7 +17,7 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_identifier) { "project-#{expected_id}" }
let(:expected_suffix) { '' }
let(:expected_container) { project }
let(:expected_repository) { expected_container.repository }
let(:expected_repository) { ::Repository.new(project.full_path, project, shard: project.repository_storage, disk_path: project.disk_path, repo_type: Gitlab::GlRepository::PROJECT) }
end
it 'knows its type' do
@ -46,7 +46,7 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_identifier) { "wiki-#{expected_id}" }
let(:expected_suffix) { '.wiki' }
let(:expected_container) { project }
let(:expected_repository) { expected_container.wiki.repository }
let(:expected_repository) { ::Repository.new(project.wiki.full_path, project, shard: project.wiki.repository_storage, disk_path: project.wiki.disk_path, repo_type: Gitlab::GlRepository::WIKI) }
end
it 'knows its type' do
@ -75,7 +75,7 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_id) { personal_snippet.id }
let(:expected_identifier) { "snippet-#{expected_id}" }
let(:expected_suffix) { '' }
let(:expected_repository) { personal_snippet.repository }
let(:expected_repository) { ::Repository.new(personal_snippet.full_path, personal_snippet, shard: personal_snippet.repository_storage, disk_path: personal_snippet.disk_path, repo_type: Gitlab::GlRepository::SNIPPET) }
let(:expected_container) { personal_snippet }
end
@ -104,7 +104,7 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_id) { project_snippet.id }
let(:expected_identifier) { "snippet-#{expected_id}" }
let(:expected_suffix) { '' }
let(:expected_repository) { project_snippet.repository }
let(:expected_repository) { ::Repository.new(project_snippet.full_path, project_snippet, shard: project_snippet.repository_storage, disk_path: project_snippet.disk_path, repo_type: Gitlab::GlRepository::SNIPPET) }
let(:expected_container) { project_snippet }
end
@ -133,10 +133,14 @@ RSpec.describe Gitlab::GlRepository::RepoType do
let(:expected_identifier) { "design-#{project.id}" }
let(:expected_id) { project.id }
let(:expected_suffix) { '.design' }
let(:expected_repository) { project.design_repository }
let(:expected_repository) { ::DesignManagement::Repository.new(project) }
let(:expected_container) { project }
end
it 'uses the design access checker' do
expect(described_class.access_checker_class).to eq(::Gitlab::GitAccessDesign)
end
it 'knows its type' do
aggregate_failures do
expect(described_class).to be_design

View File

@ -31,15 +31,4 @@ RSpec.describe ::Gitlab::GlRepository do
expect { described_class.parse("project-foo") }.to raise_error(ArgumentError)
end
end
describe 'DESIGN' do
it 'uses the design access checker' do
expect(described_class::DESIGN.access_checker_class).to eq(::Gitlab::GitAccessDesign)
end
it 'builds a design repository' do
expect(described_class::DESIGN.repository_resolver.call(create(:project)))
.to be_a(::DesignManagement::Repository)
end
end
end

View File

@ -1179,7 +1179,7 @@ RSpec.describe API::Internal::Base do
let(:gl_repository) { "snippet-#{personal_snippet.id}" }
it 'does not try to notify that project moved' do
allow(Gitlab::GlRepository).to receive(:parse).and_return([personal_snippet, nil, Gitlab::GlRepository::PROJECT])
allow(Gitlab::GlRepository).to receive(:parse).and_return([personal_snippet, nil, Gitlab::GlRepository::SNIPPET])
expect(Gitlab::Checks::ProjectMoved).not_to receive(:fetch_message)

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
require 'rubocop'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/migration/update_column_in_batches'
RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches, type: :rubocop do
let(:cop) { described_class.new }
let(:tmp_rails_root) { Rails.root.join('tmp', 'rails_root') }
let(:tmp_rails_root) { rails_root_join('tmp', 'rails_root') }
let(:migration_code) do
<<-END
def up
@ -27,7 +27,7 @@ RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
FileUtils.rm_rf(tmp_rails_root)
end
let(:spec_filepath) { tmp_rails_root.join('spec', 'migrations', 'my_super_migration_spec.rb') }
let(:spec_filepath) { File.join(tmp_rails_root, 'spec', 'migrations', 'my_super_migration_spec.rb') }
context 'outside of a migration' do
it 'does not register any offenses' do
@ -83,31 +83,31 @@ RSpec.describe RuboCop::Cop::Migration::UpdateColumnInBatches do
end
context 'in a migration' do
let(:migration_filepath) { tmp_rails_root.join('db', 'migrate', '20121220064453_my_super_migration.rb') }
let(:migration_filepath) { File.join(tmp_rails_root, 'db', 'migrate', '20121220064453_my_super_migration.rb') }
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'
end
context 'in a post migration' do
let(:migration_filepath) { tmp_rails_root.join('db', 'post_migrate', '20121220064453_my_super_migration.rb') }
let(:migration_filepath) { File.join(tmp_rails_root, 'db', 'post_migrate', '20121220064453_my_super_migration.rb') }
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'
end
context 'EE migrations' do
let(:spec_filepath) { tmp_rails_root.join('ee', 'spec', 'migrations', 'my_super_migration_spec.rb') }
let(:spec_filepath) { File.join(tmp_rails_root, 'ee', 'spec', 'migrations', 'my_super_migration_spec.rb') }
context 'in a migration' do
let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') }
let(:migration_filepath) { File.join(tmp_rails_root, 'ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') }
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'
end
context 'in a post migration' do
let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') }
let(:migration_filepath) { File.join(tmp_rails_root, 'ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') }
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'

View File

@ -17,5 +17,9 @@ RSpec.shared_examples 'a repo type' do
it 'finds the repository for the repo type' do
expect(described_class.repository_for(expected_container)).to eq(expected_repository)
end
it 'returns nil when container is nil' do
expect(described_class.repository_for(nil)).to eq(nil)
end
end
end