Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
505cb2114d
commit
38d4874ab9
32 changed files with 363 additions and 130 deletions
|
@ -11,3 +11,4 @@ export const TYPE_SCANNER_PROFILE = 'DastScannerProfile';
|
|||
export const TYPE_SITE_PROFILE = 'DastSiteProfile';
|
||||
export const TYPE_USER = 'User';
|
||||
export const TYPE_VULNERABILITY = 'Vulnerability';
|
||||
export const TYPE_NOTE = 'Note';
|
||||
|
|
|
@ -232,7 +232,9 @@ export function insertMarkdownText({
|
|||
.join('\n');
|
||||
}
|
||||
} else if (tag.indexOf(textPlaceholder) > -1) {
|
||||
textToInsert = tag.replace(textPlaceholder, () => selected.replace(/\\n/g, '\n'));
|
||||
textToInsert = tag.replace(textPlaceholder, () =>
|
||||
selected.replace(/\\n/g, '\n').replace(/\/(n|t|r)/g, '\\$1'),
|
||||
);
|
||||
} else {
|
||||
textToInsert = String(startChar) + tag + selected + (wrap ? tag : '');
|
||||
}
|
||||
|
|
|
@ -15,6 +15,14 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
|||
import MarkdownHeader from './header.vue';
|
||||
import MarkdownToolbar from './toolbar.vue';
|
||||
|
||||
function cleanUpLine(content) {
|
||||
return unescape(
|
||||
stripHtml(content)
|
||||
.replace(/\\(n|t|r)/g, '/$1')
|
||||
.replace(/\n/g, ''),
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GfmAutocomplete,
|
||||
|
@ -129,7 +137,7 @@ export default {
|
|||
return text;
|
||||
}
|
||||
|
||||
return unescape(stripHtml(richText).replace(/\n/g, ''));
|
||||
return cleanUpLine(richText);
|
||||
})
|
||||
.join('\\n');
|
||||
}
|
||||
|
@ -141,7 +149,7 @@ export default {
|
|||
return text;
|
||||
}
|
||||
|
||||
return unescape(stripHtml(richText).replace(/\n/g, ''));
|
||||
return cleanUpLine(richText);
|
||||
}
|
||||
|
||||
return '';
|
||||
|
@ -272,6 +280,7 @@ export default {
|
|||
:can-suggest="canSuggest"
|
||||
:show-suggest-popover="showSuggestPopover"
|
||||
:suggestion-start-index="suggestionsStartIndex"
|
||||
data-testid="markdownHeader"
|
||||
@preview-markdown="showPreviewTab"
|
||||
@write-markdown="showWriteTab"
|
||||
@handleSuggestDismissed="() => $emit('handleSuggestDismissed')"
|
||||
|
|
|
@ -297,7 +297,10 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def index_params
|
||||
params.permit(:scope, :username, :ref, :status)
|
||||
permitted_params = [:scope, :username, :ref, :status]
|
||||
permitted_params << :source if Feature.enabled?(:pipeline_source_filter, project, default_enabled: :yaml)
|
||||
|
||||
params.permit(*permitted_params)
|
||||
end
|
||||
|
||||
def enable_code_quality_walkthrough_experiment
|
||||
|
|
|
@ -306,7 +306,9 @@ module Ci
|
|||
end
|
||||
|
||||
after_transition pending: :running do |build|
|
||||
build.deployment&.run
|
||||
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
|
||||
build.deployment&.run
|
||||
end
|
||||
|
||||
build.run_after_commit do
|
||||
build.pipeline.persistent_ref.create
|
||||
|
@ -328,7 +330,9 @@ module Ci
|
|||
end
|
||||
|
||||
after_transition any => [:success] do |build|
|
||||
build.deployment&.succeed
|
||||
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
|
||||
build.deployment&.succeed
|
||||
end
|
||||
|
||||
build.run_after_commit do
|
||||
BuildSuccessWorker.perform_async(id)
|
||||
|
@ -341,7 +345,9 @@ module Ci
|
|||
next unless build.deployment
|
||||
|
||||
begin
|
||||
build.deployment.drop!
|
||||
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
|
||||
build.deployment.drop!
|
||||
end
|
||||
rescue StandardError => e
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, build_id: build.id)
|
||||
end
|
||||
|
@ -362,10 +368,12 @@ module Ci
|
|||
end
|
||||
|
||||
after_transition any => [:skipped, :canceled] do |build, transition|
|
||||
if transition.to_name == :skipped
|
||||
build.deployment&.skip
|
||||
else
|
||||
build.deployment&.cancel
|
||||
Gitlab::Database.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/338867') do
|
||||
if transition.to_name == :skipped
|
||||
build.deployment&.skip
|
||||
else
|
||||
build.deployment&.cancel
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,6 +83,10 @@ module Featurable
|
|||
end
|
||||
end
|
||||
|
||||
included do
|
||||
validate :allowed_access_levels
|
||||
end
|
||||
|
||||
def access_level(feature)
|
||||
public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
@ -94,4 +98,21 @@ module Featurable
|
|||
def string_access_level(feature)
|
||||
self.class.str_from_access_level(access_level(feature))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed_access_levels
|
||||
validator = lambda do |field|
|
||||
level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
|
||||
not_allowed = level > ENABLED
|
||||
self.errors.add(field, "cannot have public visibility level") if not_allowed
|
||||
end
|
||||
|
||||
(self.class.available_features - feature_validation_exclusion).each {|f| validator.call("#{f}_access_level")}
|
||||
end
|
||||
|
||||
# Features that we should exclude from the validation
|
||||
def feature_validation_exclusion
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,7 +54,6 @@ class ProjectFeature < ApplicationRecord
|
|||
validates :project, presence: true
|
||||
|
||||
validate :repository_children_level
|
||||
validate :allowed_access_levels
|
||||
|
||||
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
|
||||
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
|
||||
|
@ -110,17 +109,6 @@ class ProjectFeature < ApplicationRecord
|
|||
%i(merge_requests_access_level builds_access_level).each(&validator)
|
||||
end
|
||||
|
||||
# Validates access level for other than pages cannot be PUBLIC
|
||||
def allowed_access_levels
|
||||
validator = lambda do |field|
|
||||
level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
|
||||
not_allowed = level > ENABLED
|
||||
self.errors.add(field, "cannot have public visibility level") if not_allowed
|
||||
end
|
||||
|
||||
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
|
||||
end
|
||||
|
||||
def get_permission(user, feature)
|
||||
case access_level(feature)
|
||||
when DISABLED
|
||||
|
@ -142,6 +130,10 @@ class ProjectFeature < ApplicationRecord
|
|||
|
||||
project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
|
||||
end
|
||||
|
||||
def feature_validation_exclusion
|
||||
%i(pages)
|
||||
end
|
||||
end
|
||||
|
||||
ProjectFeature.prepend_mod_with('ProjectFeature')
|
||||
|
|
|
@ -10,16 +10,9 @@ module Ci
|
|||
private
|
||||
|
||||
def process_subsequent_jobs(processable)
|
||||
if Feature.enabled?(:ci_same_stage_job_needs, processable.project, default_enabled: :yaml)
|
||||
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
|
||||
.each do |processable|
|
||||
process(processable)
|
||||
end
|
||||
else
|
||||
skipped_jobs(processable).after_stage(processable.stage_idx)
|
||||
.find_each do |job|
|
||||
process(job)
|
||||
end
|
||||
(stage_dependent_jobs(processable) | needs_dependent_jobs(processable))
|
||||
.each do |processable|
|
||||
process(processable)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: ci_same_stage_job_needs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59668
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/328253
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveAllowEditingCommitMessagesFromProjectSettings < ActiveRecord::Migration[6.1]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_column :project_settings, :allow_editing_commit_messages
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
add_column :project_settings, :allow_editing_commit_messages, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
||||
end
|
1
db/schema_migrations/20210823193234
Normal file
1
db/schema_migrations/20210823193234
Normal file
|
@ -0,0 +1 @@
|
|||
b85ef326056bb152d527e34b49caa3c40ee8685c3b14654992246c6adf082f8c
|
|
@ -17343,7 +17343,6 @@ CREATE TABLE project_settings (
|
|||
squash_option smallint DEFAULT 3,
|
||||
has_confluence boolean DEFAULT false NOT NULL,
|
||||
has_vulnerabilities boolean DEFAULT false NOT NULL,
|
||||
allow_editing_commit_messages boolean DEFAULT false NOT NULL,
|
||||
prevent_merge_without_jira_issue boolean DEFAULT false NOT NULL,
|
||||
cve_id_request_enabled boolean DEFAULT true NOT NULL,
|
||||
mr_default_target_self boolean DEFAULT false NOT NULL,
|
||||
|
|
|
@ -55,6 +55,12 @@ zcat @400000006026b71d1a7af804.s | (head -1; tail -1) | jq '.time'
|
|||
zcat some_json.log.25.gz | (head -1; tail -1) | jq '.time'
|
||||
```
|
||||
|
||||
#### Get activity for correlation ID across multiple JSON logs in chronological order
|
||||
|
||||
```shell
|
||||
grep -hR <correlationID> | jq -c -R 'fromjson?' | jq -C -s 'sort_by(.time)' | less -R
|
||||
```
|
||||
|
||||
### Parsing `production_json.log` and `api_json.log`
|
||||
|
||||
#### Find all requests with a 5XX status code
|
||||
|
|
|
@ -1594,8 +1594,7 @@ production:
|
|||
- In [GitLab 14.1 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632) you
|
||||
can refer to jobs in the same stage as the job you are configuring. This feature is
|
||||
enabled on GitLab.com and ready for production use. On self-managed [GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/30632)
|
||||
this feature is available by default. To hide the feature, ask an administrator to
|
||||
[disable the `ci_same_stage_job_needs` flag](../../administration/feature_flags.md).
|
||||
this feature is available by default.
|
||||
- In GitLab 14.0 and older, you can only refer to jobs in earlier stages.
|
||||
- In GitLab 13.9 and older, if `needs:` refers to a job that might not be added to
|
||||
a pipeline because of `only`, `except`, or `rules`, the pipeline might fail to create.
|
||||
|
|
|
@ -15,12 +15,7 @@ module Gitlab
|
|||
@context = context
|
||||
@pipeline = context.pipeline
|
||||
@seed_attributes = attributes
|
||||
@stages_for_needs_lookup = if Feature.enabled?(:ci_same_stage_job_needs, @pipeline.project, default_enabled: :yaml)
|
||||
(previous_stages + [current_stage]).compact
|
||||
else
|
||||
previous_stages
|
||||
end
|
||||
|
||||
@stages_for_needs_lookup = (previous_stages + [current_stage]).compact
|
||||
@needs_attributes = dig(:needs_attributes)
|
||||
@resource_group_key = attributes.delete(:resource_group_key)
|
||||
@job_variables = @seed_attributes.delete(:job_variables)
|
||||
|
|
|
@ -47,9 +47,7 @@ module Gitlab
|
|||
validate_job!(name, job)
|
||||
end
|
||||
|
||||
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
|
||||
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
|
||||
end
|
||||
YamlProcessor::Dag.check_circular_dependencies!(@jobs)
|
||||
end
|
||||
|
||||
def validate_job!(name, job)
|
||||
|
@ -103,16 +101,8 @@ module Gitlab
|
|||
job_stage_index = stage_index(name)
|
||||
dependency_stage_index = stage_index(dependency)
|
||||
|
||||
if ::Feature.enabled?(:ci_same_stage_job_needs, @opts[:project], default_enabled: :yaml)
|
||||
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
|
||||
end
|
||||
else
|
||||
# A dependency might be defined later in the configuration
|
||||
# with a stage that does not exist
|
||||
unless dependency_stage_index.present? && dependency_stage_index < job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in prior stages")
|
||||
end
|
||||
unless dependency_stage_index.present? && dependency_stage_index <= job_stage_index
|
||||
error!("#{name} job: #{dependency_type} #{dependency} is not defined in current or prior stages")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -147,9 +147,16 @@ module Gitlab
|
|||
# spec/support/database/prevent_cross_joins.rb
|
||||
end
|
||||
|
||||
# This method will allow cross database modifications within the block
|
||||
# Example:
|
||||
#
|
||||
# allow_cross_database_modification_within_transaction(url: 'url-to-an-issue') do
|
||||
# create(:build) # inserts ci_build and project record in one transaction
|
||||
# end
|
||||
def self.allow_cross_database_modification_within_transaction(url:)
|
||||
# this method is implemented in:
|
||||
# this method will be overridden in:
|
||||
# spec/support/database/cross_database_modification_check.rb
|
||||
yield
|
||||
end
|
||||
|
||||
def self.add_post_migrate_path_to_rails(force: false)
|
||||
|
|
30
lib/gitlab/pagination/keyset/cursor_based_request_context.rb
Normal file
30
lib/gitlab/pagination/keyset/cursor_based_request_context.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Pagination
|
||||
module Keyset
|
||||
class CursorBasedRequestContext
|
||||
attr_reader :request
|
||||
delegate :params, :header, to: :request
|
||||
|
||||
def initialize(request)
|
||||
@request = request
|
||||
end
|
||||
|
||||
def per_page
|
||||
params[:per_page]
|
||||
end
|
||||
|
||||
def cursor
|
||||
params[:cursor]
|
||||
end
|
||||
|
||||
def apply_headers(cursor_for_next_page)
|
||||
Gitlab::Pagination::Keyset::HeaderBuilder
|
||||
.new(self)
|
||||
.add_next_page_header({ cursor: cursor_for_next_page })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
38
lib/gitlab/pagination/keyset/cursor_pager.rb
Normal file
38
lib/gitlab/pagination/keyset/cursor_pager.rb
Normal file
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Pagination
|
||||
module Keyset
|
||||
class CursorPager < Gitlab::Pagination::Base
|
||||
attr_reader :cursor_based_request_context, :paginator
|
||||
|
||||
def initialize(cursor_based_request_context)
|
||||
@cursor_based_request_context = cursor_based_request_context
|
||||
end
|
||||
|
||||
def paginate(relation)
|
||||
@paginator ||= relation.keyset_paginate(
|
||||
per_page: cursor_based_request_context.per_page,
|
||||
cursor: cursor_based_request_context.cursor
|
||||
)
|
||||
|
||||
paginator.records
|
||||
end
|
||||
|
||||
def finalize(_records = [])
|
||||
# can be called only after executing `paginate(relation)`
|
||||
apply_headers
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def apply_headers
|
||||
return unless paginator.has_next_page?
|
||||
|
||||
cursor_based_request_context
|
||||
.apply_headers(paginator.cursor_for_next_page)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -88,6 +88,25 @@ describe('init markdown', () => {
|
|||
expect(textArea.value).toEqual(`${initialValue}\n- `);
|
||||
});
|
||||
|
||||
it('unescapes new line characters', () => {
|
||||
const initialValue = '';
|
||||
|
||||
textArea.value = initialValue;
|
||||
textArea.selectionStart = 0;
|
||||
textArea.selectionEnd = 0;
|
||||
|
||||
insertMarkdownText({
|
||||
textArea,
|
||||
text: textArea.value,
|
||||
tag: '```suggestion:-0+0\n{text}\n```',
|
||||
blockTag: true,
|
||||
selected: '# Does not parse the /n currently.',
|
||||
wrap: false,
|
||||
});
|
||||
|
||||
expect(textArea.value).toContain('# Does not parse the \\n currently.');
|
||||
});
|
||||
|
||||
it('inserts the tag on the same line if the current line only contains spaces', () => {
|
||||
const initialValue = ' ';
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
|
|||
data-uploads-path=""
|
||||
>
|
||||
<markdown-header-stub
|
||||
data-testid="markdownHeader"
|
||||
linecontent=""
|
||||
suggestionstartindex="0"
|
||||
/>
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('Markdown field component', () => {
|
|||
axiosMock.restore();
|
||||
});
|
||||
|
||||
function createSubject() {
|
||||
function createSubject(lines = []) {
|
||||
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
|
||||
// caused by mixing Vanilla JS and Vue.
|
||||
subject = mount(
|
||||
|
@ -60,6 +60,7 @@ describe('Markdown field component', () => {
|
|||
markdownPreviewPath,
|
||||
isSubmitting: false,
|
||||
textareaValue,
|
||||
lines,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
@ -243,4 +244,14 @@ describe('Markdown field component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('suggestions', () => {
|
||||
it('escapes new line characters', () => {
|
||||
createSubject([{ rich_text: 'hello world\\n' }]);
|
||||
|
||||
expect(subject.find('[data-testid="markdownHeader"]').props('lineContent')).toBe(
|
||||
'hello world/n',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1140,16 +1140,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
|
|||
it 'does not have errors' do
|
||||
expect(subject.errors).to be_empty
|
||||
end
|
||||
|
||||
context 'when ci_same_stage_job_needs FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it 'has errors' do
|
||||
expect(subject.errors).to contain_exactly("'rspec' job needs 'build' job, but 'build' is not in any previous stage")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using 101 needs' do
|
||||
|
|
|
@ -34,10 +34,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
|
|||
described_class.new(seed_context, stages_attributes)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
describe '#stages' do
|
||||
it 'returns the stage resources' do
|
||||
stages = seed.stages
|
||||
|
|
|
@ -590,14 +590,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in current or prior stages/
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'has warnings and expected error', /build job: need test is not defined in prior stages/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1809,14 +1801,6 @@ module Gitlab
|
|||
let(:dependencies) { ['deploy'] }
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in current or prior stages'
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: dependency deploy is not defined in prior stages'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a job depends on another job that references a not-yet defined stage' do
|
||||
|
@ -2053,14 +2037,6 @@ module Gitlab
|
|||
let(:needs) { ['deploy'] }
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in current or prior stages'
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'returns errors', 'test1 job: need deploy is not defined in prior stages'
|
||||
end
|
||||
end
|
||||
|
||||
context 'needs and dependencies that are mismatching' do
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Pagination::Keyset::CursorBasedRequestContext do
|
||||
let(:params) { { per_page: 2, cursor: 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' } }
|
||||
let(:request) { double('request', params: params) }
|
||||
|
||||
describe '#per_page' do
|
||||
subject(:per_page) { described_class.new(request).per_page }
|
||||
|
||||
it { is_expected.to eq 2 }
|
||||
end
|
||||
|
||||
describe '#cursor' do
|
||||
subject(:cursor) { described_class.new(request).cursor }
|
||||
|
||||
it { is_expected.to eq 'eyJuYW1lIjoiR2l0TGFiIEluc3RhbmNlIiwiaWQiOiI1MiIsIl9rZCI6Im4ifQ==' }
|
||||
end
|
||||
|
||||
describe '#apply_headers' do
|
||||
let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?per_page=3", params: params) }
|
||||
let(:params) { { per_page: 3 } }
|
||||
let(:cursor_for_next_page) { 'eyJuYW1lIjoiSDVicCIsImlkIjoiMjgiLCJfa2QiOiJuIn0=' }
|
||||
|
||||
subject(:apply_headers) { described_class.new(request).apply_headers(cursor_for_next_page) }
|
||||
|
||||
it 'sets Link header with same host/path as the original request' do
|
||||
orig_uri = URI.parse(request.url)
|
||||
|
||||
expect(request).to receive(:header).once do |name, header|
|
||||
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
|
||||
|
||||
uri = URI.parse(first_link)
|
||||
|
||||
expect(name).to eq('Link')
|
||||
expect(uri.host).to eq(orig_uri.host)
|
||||
expect(uri.path).to eq(orig_uri.path)
|
||||
end
|
||||
|
||||
apply_headers
|
||||
end
|
||||
|
||||
it 'sets Link header with a cursor to the next page' do
|
||||
orig_uri = URI.parse(request.url)
|
||||
|
||||
expect(request).to receive(:header).once do |name, header|
|
||||
first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures
|
||||
|
||||
query = CGI.parse(URI.parse(first_link).query)
|
||||
|
||||
expect(name).to eq('Link')
|
||||
expect(query.except('cursor')).to eq(CGI.parse(orig_uri.query).except('cursor'))
|
||||
expect(query['cursor']).to eq([cursor_for_next_page])
|
||||
end
|
||||
|
||||
apply_headers
|
||||
end
|
||||
end
|
||||
end
|
63
spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb
Normal file
63
spec/lib/gitlab/pagination/keyset/cursor_pager_spec.rb
Normal file
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Pagination::Keyset::CursorPager do
|
||||
let(:relation) { Group.all.order(:name, :id) }
|
||||
let(:per_page) { 3 }
|
||||
let(:params) { { cursor: nil, per_page: per_page } }
|
||||
let(:request) { double('request', params: params) }
|
||||
let(:cursor_based_request_context) { Gitlab::Pagination::Keyset::CursorBasedRequestContext.new(request) }
|
||||
|
||||
before_all do
|
||||
create_list(:group, 7)
|
||||
end
|
||||
|
||||
describe '#paginate' do
|
||||
subject(:paginated_result) { described_class.new(cursor_based_request_context).paginate(relation) }
|
||||
|
||||
it 'returns the limited relation' do
|
||||
expect(paginated_result).to eq(relation.limit(per_page))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#finalize' do
|
||||
subject(:finalize) do
|
||||
service = described_class.new(cursor_based_request_context)
|
||||
# we need to do this because `finalize` can only be called
|
||||
# after `paginate` is called. Otherwise the `paginator` object won't be set.
|
||||
service.paginate(relation)
|
||||
service.finalize
|
||||
end
|
||||
|
||||
it 'passes information about next page to request' do
|
||||
cursor_for_next_page = relation.keyset_paginate(**params).cursor_for_next_page
|
||||
|
||||
expect_next_instance_of(Gitlab::Pagination::Keyset::HeaderBuilder, cursor_based_request_context) do |builder|
|
||||
expect(builder).to receive(:add_next_page_header).with({ cursor: cursor_for_next_page })
|
||||
end
|
||||
|
||||
finalize
|
||||
end
|
||||
|
||||
context 'when retrieving the last page' do
|
||||
let(:relation) { Group.where('id > ?', Group.maximum(:id) - per_page).order(:name, :id) }
|
||||
|
||||
it 'does not build information about the next page' do
|
||||
expect(Gitlab::Pagination::Keyset::HeaderBuilder).not_to receive(:new)
|
||||
|
||||
finalize
|
||||
end
|
||||
end
|
||||
|
||||
context 'when retrieving an empty page' do
|
||||
let(:relation) { Group.where('id > ?', Group.maximum(:id) + 1).order(:name, :id) }
|
||||
|
||||
it 'does not build information about the next page' do
|
||||
expect(Gitlab::Pagination::Keyset::HeaderBuilder).not_to receive(:new)
|
||||
|
||||
finalize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1307,7 +1307,9 @@ RSpec.describe Ci::Build do
|
|||
|
||||
shared_examples_for 'avoid deadlock' do
|
||||
it 'executes UPDATE in the right order' do
|
||||
recorded = ActiveRecord::QueryRecorder.new { subject }
|
||||
recorded = with_cross_database_modification_prevented do
|
||||
ActiveRecord::QueryRecorder.new { subject }
|
||||
end
|
||||
|
||||
index_for_build = recorded.log.index { |l| l.include?("UPDATE \"ci_builds\"") }
|
||||
index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
|
||||
|
@ -1322,7 +1324,9 @@ RSpec.describe Ci::Build do
|
|||
it_behaves_like 'avoid deadlock'
|
||||
|
||||
it 'transits deployment status to running' do
|
||||
subject
|
||||
with_cross_database_modification_prevented do
|
||||
subject
|
||||
end
|
||||
|
||||
expect(deployment).to be_running
|
||||
end
|
||||
|
@ -1340,7 +1344,9 @@ RSpec.describe Ci::Build do
|
|||
it_behaves_like 'calling proper BuildFinishedWorker'
|
||||
|
||||
it 'transits deployment status to success' do
|
||||
subject
|
||||
with_cross_database_modification_prevented do
|
||||
subject
|
||||
end
|
||||
|
||||
expect(deployment).to be_success
|
||||
end
|
||||
|
@ -1353,7 +1359,9 @@ RSpec.describe Ci::Build do
|
|||
it_behaves_like 'calling proper BuildFinishedWorker'
|
||||
|
||||
it 'transits deployment status to failed' do
|
||||
subject
|
||||
with_cross_database_modification_prevented do
|
||||
subject
|
||||
end
|
||||
|
||||
expect(deployment).to be_failed
|
||||
end
|
||||
|
@ -1365,7 +1373,9 @@ RSpec.describe Ci::Build do
|
|||
it_behaves_like 'avoid deadlock'
|
||||
|
||||
it 'transits deployment status to skipped' do
|
||||
subject
|
||||
with_cross_database_modification_prevented do
|
||||
subject
|
||||
end
|
||||
|
||||
expect(deployment).to be_skipped
|
||||
end
|
||||
|
@ -1378,7 +1388,9 @@ RSpec.describe Ci::Build do
|
|||
it_behaves_like 'calling proper BuildFinishedWorker'
|
||||
|
||||
it 'transits deployment status to canceled' do
|
||||
subject
|
||||
with_cross_database_modification_prevented do
|
||||
subject
|
||||
end
|
||||
|
||||
expect(deployment).to be_canceled
|
||||
end
|
||||
|
|
|
@ -30,8 +30,11 @@ RSpec.describe Featurable do
|
|||
|
||||
describe '.set_available_features' do
|
||||
let!(:klass) do
|
||||
Class.new do
|
||||
Class.new(ApplicationRecord) do
|
||||
include Featurable
|
||||
|
||||
self.table_name = 'project_features'
|
||||
|
||||
set_available_features %i(feature1 feature2)
|
||||
|
||||
def feature1_access_level
|
||||
|
|
|
@ -41,18 +41,15 @@ RSpec.describe ProjectFeature do
|
|||
end
|
||||
end
|
||||
|
||||
context 'public features' do
|
||||
features = ProjectFeature::FEATURES - %i(pages)
|
||||
it_behaves_like 'access level validation', ProjectFeature::FEATURES - %i(pages) do
|
||||
let(:container_features) { project.project_feature }
|
||||
end
|
||||
|
||||
features.each do |feature|
|
||||
it "does not allow public access level for #{feature}" do
|
||||
project_feature = project.project_feature
|
||||
field = "#{feature}_access_level".to_sym
|
||||
project_feature.update_attribute(field, ProjectFeature::PUBLIC)
|
||||
it 'allows public access level for :pages feature' do
|
||||
project_feature = project.project_feature
|
||||
project_feature.pages_access_level = ProjectFeature::PUBLIC
|
||||
|
||||
expect(project_feature.valid?).to be_falsy, "#{field} failed"
|
||||
end
|
||||
end
|
||||
expect(project_feature.valid?).to be_truthy
|
||||
end
|
||||
|
||||
describe 'default pages access level' do
|
||||
|
|
|
@ -44,16 +44,6 @@ RSpec.describe Ci::AfterRequeueJobService do
|
|||
it 'marks subsequent skipped jobs as processable' do
|
||||
expect { execute_service }.to change { test4.reload.status }.from('skipped').to('created')
|
||||
end
|
||||
|
||||
context 'with ci_same_stage_job_needs FF disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_same_stage_job_needs: false)
|
||||
end
|
||||
|
||||
it 'does nothing with the build' do
|
||||
expect { execute_service }.not_to change { test4.reload.status }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline is a downstream pipeline and the bridge is depended' do
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'access level validation' do |features|
|
||||
features.each do |feature|
|
||||
it "does not allow public access level for #{feature}" do
|
||||
field = "#{feature}_access_level".to_sym
|
||||
container_features.update_attribute(field, ProjectFeature::PUBLIC)
|
||||
|
||||
expect(container_features.valid?).to be_falsy, "#{field} failed"
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue