Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
671c8718a9
commit
3c0faf1c6b
|
|
@ -3,3 +3,20 @@ export default function initLogoAnimation() {
|
|||
document.querySelector('.tanuki-logo')?.classList.add('animate');
|
||||
});
|
||||
}
|
||||
|
||||
export function initPortraitLogoDetection() {
|
||||
const image = document.querySelector('.js-portrait-logo-detection');
|
||||
|
||||
image?.addEventListener(
|
||||
'load',
|
||||
({ currentTarget: img }) => {
|
||||
const isPortrait = img.height > img.width;
|
||||
if (isPortrait) {
|
||||
// Limit the width when the logo has portrait format
|
||||
img.classList.replace('gl-h-9', 'gl-w-10');
|
||||
}
|
||||
img.classList.remove('gl-visibility-hidden');
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { getLocationHash, visitUrl, mergeUrlParams } from './lib/utils/url_utili
|
|||
|
||||
// everything else
|
||||
import LazyLoader from './lazy_loader';
|
||||
import initLogoAnimation from './logo';
|
||||
import initLogoAnimation, { initPortraitLogoDetection } from './logo';
|
||||
import initBreadcrumbs from './breadcrumb';
|
||||
import initPersistentUserCallouts from './persistent_user_callouts';
|
||||
import { initUserTracking, initDefaultTrackers } from './tracking';
|
||||
|
|
@ -83,6 +83,7 @@ function deferredInitialisation() {
|
|||
initBreadcrumbs();
|
||||
initPrefetchLinks('.js-prefetch-document');
|
||||
initLogoAnimation();
|
||||
initPortraitLogoDetection();
|
||||
initUserPopovers();
|
||||
initBroadcastNotifications();
|
||||
initPersistentUserCallouts();
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ module AppearancesHelper
|
|||
end
|
||||
|
||||
def brand_image
|
||||
image_tag(brand_image_path, alt: brand_title, class: 'gl-w-10')
|
||||
image_tag(brand_image_path, alt: brand_title, class: 'gl-visibility-hidden gl-h-9 js-portrait-logo-detection')
|
||||
end
|
||||
|
||||
def brand_image_path
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ class MergeRequestPolicy < IssuablePolicy
|
|||
prevent :accept_merge_request
|
||||
end
|
||||
|
||||
rule { can?(:read_merge_request) }.policy do
|
||||
enable :generate_diff_summary
|
||||
end
|
||||
|
||||
rule { can_approve }.policy do
|
||||
enable :approve_merge_request
|
||||
end
|
||||
|
|
@ -47,10 +43,6 @@ class MergeRequestPolicy < IssuablePolicy
|
|||
enable :set_merge_request_metadata
|
||||
end
|
||||
|
||||
rule { llm_bot }.policy do
|
||||
enable :generate_diff_summary
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def can_approve?
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ module Groups
|
|||
) do
|
||||
Group.transaction do
|
||||
update_group_attributes
|
||||
remove_paid_features_for_projects(old_root_ancestor_id)
|
||||
ensure_ownership
|
||||
update_integrations
|
||||
remove_issue_contacts(old_root_ancestor_id, was_root_group)
|
||||
|
|
@ -74,6 +73,7 @@ module Groups
|
|||
end
|
||||
end
|
||||
|
||||
remove_paid_features_for_projects(old_root_ancestor_id)
|
||||
post_update_hooks(@updated_project_ids, old_root_ancestor_id)
|
||||
propagate_integrations
|
||||
update_pending_builds
|
||||
|
|
|
|||
|
|
@ -131,8 +131,6 @@ module Projects
|
|||
|
||||
update_integrations
|
||||
|
||||
remove_paid_features
|
||||
|
||||
project.old_path_with_namespace = @old_path
|
||||
|
||||
update_repository_configuration(@new_path)
|
||||
|
|
@ -143,6 +141,7 @@ module Projects
|
|||
end
|
||||
end
|
||||
|
||||
remove_paid_features
|
||||
update_pending_builds
|
||||
|
||||
post_update_hooks(project, @old_group)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
table_name: audit_events_streaming_http_instance_namespace_filters
|
||||
classes:
|
||||
- AuditEvents::Streaming::HTTP::Instance::NamespaceFilter
|
||||
feature_categories:
|
||||
- audit_events
|
||||
description: Represents a group or project filter for instance-level custom http external audit event destinations.
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136959
|
||||
milestone: '16.7'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
sharding_key:
|
||||
namespace_id: namespaces
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
migration_job_name: BackfillBranchProtectionNamespaceSetting
|
||||
description: This migration back fills column default_branch_protection_defaults of namespace settings table
|
||||
feature_category: source_code_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136181
|
||||
milestone: '16.7'
|
||||
queued_migration_version: 20231107092912
|
||||
finalize_after: '2024-01-23'
|
||||
finalized_by: # version of the migration that ensured this bbm
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateAuditEventsStreamingHttpInstanceNamespaceFilters < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.7'
|
||||
enable_lock_retries!
|
||||
|
||||
UNIQ_DESTINATION_INDEX_NAME = 'unique_audit_events_instance_namespace_filters_destination_id'
|
||||
NAMESPACE_INDEX_NAME = 'index_audit_events_instance_namespace_filters_on_namespace_id'
|
||||
|
||||
def change
|
||||
create_table :audit_events_streaming_http_instance_namespace_filters do |t|
|
||||
t.timestamps_with_timezone null: false
|
||||
t.bigint :audit_events_instance_external_audit_event_destination_id,
|
||||
null: false,
|
||||
index: { unique: true, name: UNIQ_DESTINATION_INDEX_NAME }
|
||||
t.bigint :namespace_id,
|
||||
null: false,
|
||||
index: { name: NAMESPACE_INDEX_NAME }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddDestinationFkToAuditEventsHttpInstanceNamespaceFilters < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.7'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :audit_events_streaming_http_instance_namespace_filters,
|
||||
:audit_events_instance_external_audit_event_destinations,
|
||||
column: :audit_events_instance_external_audit_event_destination_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :audit_events_streaming_http_instance_namespace_filters,
|
||||
column: :audit_events_instance_external_audit_event_destination_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddNamespaceFkToAuditEventsHttpInstanceNamespaceFilters < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.7'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :audit_events_streaming_http_instance_namespace_filters,
|
||||
:namespaces,
|
||||
column: :namespace_id,
|
||||
on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists :audit_events_streaming_http_instance_namespace_filters,
|
||||
column: :namespace_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillBranchProtectionNamespaceSetting < Gitlab::Database::Migration[2.2]
|
||||
milestone "16.7"
|
||||
MIGRATION = "BackfillBranchProtectionNamespaceSetting"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 10_000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:namespace_settings,
|
||||
:namespace_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :namespace_settings, :namespace_id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
f5bd273a05caa2cc662dc430cb2661321602e608054d6ef3f45dfc8cfacec152
|
||||
|
|
@ -0,0 +1 @@
|
|||
3f8ea307440353cc193662c9c552609d09f7a58dd1d6acccb7208803f394329c
|
||||
|
|
@ -0,0 +1 @@
|
|||
fce9aa43da310dbbcd7d0175f531ab069548d5cc782104ab8c83f20874cc3644
|
||||
|
|
@ -0,0 +1 @@
|
|||
d5d599b967346e13f0cbdd909c33669d8a3268efc7744ca5749fab69e60ff606
|
||||
|
|
@ -12913,6 +12913,23 @@ CREATE SEQUENCE audit_events_streaming_http_group_namespace_filters_id_seq
|
|||
|
||||
ALTER SEQUENCE audit_events_streaming_http_group_namespace_filters_id_seq OWNED BY audit_events_streaming_http_group_namespace_filters.id;
|
||||
|
||||
CREATE TABLE audit_events_streaming_http_instance_namespace_filters (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
audit_events_instance_external_audit_event_destination_id bigint NOT NULL,
|
||||
namespace_id bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE audit_events_streaming_http_instance_namespace_filters_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE audit_events_streaming_http_instance_namespace_filters_id_seq OWNED BY audit_events_streaming_http_instance_namespace_filters.id;
|
||||
|
||||
CREATE TABLE audit_events_streaming_instance_event_type_filters (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
@ -26354,6 +26371,8 @@ ALTER TABLE ONLY audit_events_streaming_headers ALTER COLUMN id SET DEFAULT next
|
|||
|
||||
ALTER TABLE ONLY audit_events_streaming_http_group_namespace_filters ALTER COLUMN id SET DEFAULT nextval('audit_events_streaming_http_group_namespace_filters_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_http_instance_namespace_filters ALTER COLUMN id SET DEFAULT nextval('audit_events_streaming_http_instance_namespace_filters_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_instance_event_type_filters ALTER COLUMN id SET DEFAULT nextval('audit_events_streaming_instance_event_type_filters_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY authentication_events ALTER COLUMN id SET DEFAULT nextval('authentication_events_id_seq'::regclass);
|
||||
|
|
@ -28212,6 +28231,9 @@ ALTER TABLE ONLY audit_events_streaming_headers
|
|||
ALTER TABLE ONLY audit_events_streaming_http_group_namespace_filters
|
||||
ADD CONSTRAINT audit_events_streaming_http_group_namespace_filters_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_http_instance_namespace_filters
|
||||
ADD CONSTRAINT audit_events_streaming_http_instance_namespace_filters_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_instance_event_type_filters
|
||||
ADD CONSTRAINT audit_events_streaming_instance_event_type_filters_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
@ -31867,6 +31889,8 @@ CREATE UNIQUE INDEX index_atlassian_identities_on_extern_uid ON atlassian_identi
|
|||
|
||||
CREATE UNIQUE INDEX index_audit_events_external_audit_on_verification_token ON audit_events_external_audit_event_destinations USING btree (verification_token);
|
||||
|
||||
CREATE INDEX index_audit_events_instance_namespace_filters_on_namespace_id ON audit_events_streaming_http_instance_namespace_filters USING btree (namespace_id);
|
||||
|
||||
CREATE INDEX index_audit_events_on_entity_id_and_entity_type_and_created_at ON ONLY audit_events USING btree (entity_id, entity_type, created_at, id);
|
||||
|
||||
CREATE INDEX index_authentication_events_on_provider ON authentication_events USING btree (provider);
|
||||
|
|
@ -35421,6 +35445,8 @@ CREATE UNIQUE INDEX unique_audit_events_group_namespace_filters_destination_id O
|
|||
|
||||
CREATE UNIQUE INDEX unique_audit_events_group_namespace_filters_namespace_id ON audit_events_streaming_http_group_namespace_filters USING btree (namespace_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_audit_events_instance_namespace_filters_destination_id ON audit_events_streaming_http_instance_namespace_filters USING btree (audit_events_instance_external_audit_event_destination_id);
|
||||
|
||||
CREATE UNIQUE INDEX unique_batched_background_migrations_queued_migration_version ON batched_background_migrations USING btree (queued_migration_version);
|
||||
|
||||
CREATE UNIQUE INDEX unique_ci_builds_token_encrypted_and_partition_id ON ci_builds USING btree (token_encrypted, partition_id) WHERE (token_encrypted IS NOT NULL);
|
||||
|
|
@ -37321,6 +37347,9 @@ ALTER TABLE ONLY users_star_projects
|
|||
ALTER TABLE ONLY alert_management_alerts
|
||||
ADD CONSTRAINT fk_2358b75436 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_http_instance_namespace_filters
|
||||
ADD CONSTRAINT fk_23f3ab7df0 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY import_failures
|
||||
ADD CONSTRAINT fk_24b824da43 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -37906,6 +37935,9 @@ ALTER TABLE ONLY identities
|
|||
ALTER TABLE ONLY boards
|
||||
ADD CONSTRAINT fk_ab0a250ff6 FOREIGN KEY (iteration_cadence_id) REFERENCES iterations_cadences(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY audit_events_streaming_http_instance_namespace_filters
|
||||
ADD CONSTRAINT fk_abe44125bc FOREIGN KEY (audit_events_instance_external_audit_event_destination_id) REFERENCES audit_events_instance_external_audit_event_destinations(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY merge_requests
|
||||
ADD CONSTRAINT fk_ad525e1f87 FOREIGN KEY (merge_user_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@ module CsvBuilder
|
|||
|
||||
def row(object)
|
||||
attributes.map do |attribute|
|
||||
if object.is_a?(Hash)
|
||||
excel_sanitize(object[attribute])
|
||||
elsif attribute.respond_to?(:call)
|
||||
if attribute.respond_to?(:call)
|
||||
excel_sanitize(attribute.call(object))
|
||||
elsif object.is_a?(Hash)
|
||||
excel_sanitize(object[attribute])
|
||||
else
|
||||
excel_sanitize(object.public_send(attribute)) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe CsvBuilder do
|
||||
let(:object) { double(question: :answer) }
|
||||
let(:csv_data) { subject.render }
|
||||
let(:header_to_value_hash) do
|
||||
{ 'Q & A' => :question, 'Reversed' => ->(o) { o.question.to_s.reverse } }
|
||||
end
|
||||
|
||||
let(:subject) do
|
||||
described_class.new(
|
||||
enumerable, 'Q & A' => :question, 'Reversed' => ->(o) { o.question.to_s.reverse })
|
||||
described_class.new(enumerable, **header_to_value_hash)
|
||||
end
|
||||
|
||||
shared_examples 'csv builder examples' do
|
||||
let(:items) { [object] }
|
||||
|
||||
it "has a version number" do
|
||||
it 'has a version number' do
|
||||
expect(CsvBuilder::Version::VERSION).not_to be nil
|
||||
end
|
||||
|
||||
|
|
@ -45,30 +46,6 @@ RSpec.describe CsvBuilder do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'truncation' do
|
||||
let(:big_object) { double(question: 'Long' * 1024) }
|
||||
let(:row_size) { big_object.question.length * 2 }
|
||||
let(:items) { [big_object, big_object, big_object] }
|
||||
|
||||
it 'occurs after given number of bytes' do
|
||||
expect(subject.render(row_size * 2).length).to be_between(row_size * 2, row_size * 3)
|
||||
expect(subject).to be_truncated
|
||||
expect(subject.rows_written).to eq 2
|
||||
end
|
||||
|
||||
it 'is ignored by default' do
|
||||
expect(subject.render.length).to be > row_size * 3
|
||||
expect(subject.rows_written).to eq 3
|
||||
end
|
||||
|
||||
it 'causes rows_expected to fall back to .count' do
|
||||
subject.render(0)
|
||||
|
||||
expect(enumerable).to receive(:count).and_call_original
|
||||
expect(subject.rows_expected).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
it 'avoids loading all data in a single query' do
|
||||
expect(enumerable).to receive(:find_each)
|
||||
|
||||
|
|
@ -83,42 +60,61 @@ RSpec.describe CsvBuilder do
|
|||
expect(csv_data).to include 'answer'
|
||||
end
|
||||
|
||||
it 'allows lamdas to look up more complicated data' do
|
||||
it 'allows lambdas to look up more complicated data' do
|
||||
expect(csv_data).to include 'rewsna'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'excel sanitization' do
|
||||
let(:dangerous_title) { double(title: "=cmd|' /C calc'!A0 title", description: "*safe_desc") }
|
||||
let(:dangerous_desc) { double(title: "*safe_title", description: "=cmd|' /C calc'!A0 desc") }
|
||||
let(:items) { [dangerous_title, dangerous_desc] }
|
||||
let(:subject) { described_class.new(enumerable, 'Title' => 'title', 'Description' => 'description') }
|
||||
let(:csv_data) { subject.render }
|
||||
shared_examples 'csv builder with truncation ability' do
|
||||
let(:items) { [big_object, big_object, big_object] }
|
||||
let(:row_size) { question_value.length * 2 }
|
||||
|
||||
it 'sanitizes dangerous characters at the beginning of a column' do
|
||||
expect(csv_data).to include "'=cmd|' /C calc'!A0 title"
|
||||
expect(csv_data).to include "'=cmd|' /C calc'!A0 desc"
|
||||
end
|
||||
it 'occurs after given number of bytes' do
|
||||
expect(subject.render(row_size * 2).length).to be_between(row_size * 2, row_size * 3)
|
||||
expect(subject).to be_truncated
|
||||
expect(subject.rows_written).to eq 2
|
||||
end
|
||||
|
||||
it 'does not sanitize safe symbols at the beginning of a column' do
|
||||
expect(csv_data).not_to include "'*safe_desc"
|
||||
expect(csv_data).not_to include "'*safe_title"
|
||||
end
|
||||
it 'is ignored by default' do
|
||||
expect(subject.render.length).to be > row_size * 3
|
||||
expect(subject.rows_written).to eq 3
|
||||
end
|
||||
|
||||
context 'when dangerous characters are after a line break' do
|
||||
let(:items) { [double(title: "Safe title", description: "With task list\n-[x] todo 1")] }
|
||||
it 'causes rows_expected to fall back to .count' do
|
||||
subject.render(0)
|
||||
|
||||
it 'does not append single quote to description' do
|
||||
builder = described_class.new(enumerable, 'Title' => 'title', 'Description' => 'description')
|
||||
expect(enumerable).to receive(:count).and_call_original
|
||||
expect(subject.rows_expected).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
csv_data = builder.render
|
||||
shared_examples 'excel sanitization' do
|
||||
let(:dangerous_title) { double(title: "=cmd|' /C calc'!A0 title", description: "*safe_desc") }
|
||||
let(:dangerous_desc) { double(title: "*safe_title", description: "=cmd|' /C calc'!A0 desc") }
|
||||
let(:header_to_value_hash) { { 'Title' => 'title', 'Description' => 'description' } }
|
||||
let(:items) { [dangerous_title, dangerous_desc] }
|
||||
|
||||
expect(csv_data).to eq("Title,Description\nSafe title,\"With task list\n-[x] todo 1\"\n")
|
||||
end
|
||||
it 'sanitizes dangerous characters at the beginning of a column' do
|
||||
expect(csv_data).to include "'=cmd|' /C calc'!A0 title"
|
||||
expect(csv_data).to include "'=cmd|' /C calc'!A0 desc"
|
||||
end
|
||||
|
||||
it 'does not sanitize safe symbols at the beginning of a column' do
|
||||
expect(csv_data).not_to include "'*safe_desc"
|
||||
expect(csv_data).not_to include "'*safe_title"
|
||||
end
|
||||
|
||||
context 'when dangerous characters are after a line break' do
|
||||
let(:items) { [double(title: "Safe title", description: "With task list\n-[x] todo 1")] }
|
||||
|
||||
it 'does not append single quote to description' do
|
||||
expect(csv_data).to eq("Title,Description\nSafe title,\"With task list\n-[x] todo 1\"\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when ActiveRecord::Relation like object is given' do
|
||||
let(:object) { double(question: :answer) }
|
||||
let(:enumerable) { described_class::FakeRelation.new(items) }
|
||||
|
||||
before do
|
||||
|
|
@ -132,11 +128,43 @@ RSpec.describe CsvBuilder do
|
|||
end
|
||||
|
||||
it_behaves_like 'csv builder examples'
|
||||
it_behaves_like 'excel sanitization'
|
||||
it_behaves_like 'csv builder with truncation ability' do
|
||||
let(:big_object) { double(question: 'Long' * 1024) }
|
||||
let(:question_value) { big_object.question }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Enumerable like object is given' do
|
||||
let(:object) { double(question: :answer) }
|
||||
let(:enumerable) { items }
|
||||
|
||||
it_behaves_like 'csv builder examples'
|
||||
it_behaves_like 'excel sanitization'
|
||||
it_behaves_like 'csv builder with truncation ability' do
|
||||
let(:big_object) { double(question: 'Long' * 1024) }
|
||||
let(:question_value) { big_object.question }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Hash object is given' do
|
||||
let(:object) { { question: :answer } }
|
||||
let(:enumerable) { items }
|
||||
let(:header_to_value_hash) do
|
||||
{ 'Q & A' => :question, 'Reversed' => ->(o) { o[:question].to_s.reverse } }
|
||||
end
|
||||
|
||||
it_behaves_like 'csv builder examples'
|
||||
|
||||
it_behaves_like 'excel sanitization' do
|
||||
let(:dangerous_title) { { title: "=cmd|' /C calc'!A0 title", description: "*safe_desc" } }
|
||||
let(:dangerous_desc) { { title: "*safe_title", description: "=cmd|' /C calc'!A0 desc" } }
|
||||
let(:header_to_value_hash) { { 'Title' => :title, 'Description' => :description } }
|
||||
end
|
||||
|
||||
it_behaves_like 'csv builder with truncation ability' do
|
||||
let(:big_object) { { question: 'Long' * 1024 } }
|
||||
let(:question_value) { big_object[:question] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# This class is used to update the default_branch_protection_defaults column
|
||||
# for user namespaces of the namespace_settings table.
|
||||
class BackfillBranchProtectionNamespaceSetting < BatchedMigrationJob
|
||||
operation_name :set_default_branch_protection_defaults
|
||||
feature_category :source_code_management
|
||||
|
||||
# Migration only version of `namespaces` table
|
||||
class Namespace < ::ApplicationRecord
|
||||
self.table_name = 'namespaces'
|
||||
self.inheritance_column = :_type_disabled
|
||||
|
||||
has_one :namespace_setting,
|
||||
class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::NamespaceSetting'
|
||||
end
|
||||
|
||||
# Migration only version of `namespace_settings` table
|
||||
class NamespaceSetting < ::ApplicationRecord
|
||||
self.table_name = 'namespace_settings'
|
||||
belongs_to :namespace,
|
||||
class_name: '::Gitlab::BackgroundMigration::BackfillDefaultBranchProtectionNamespaceSetting::Namespace'
|
||||
end
|
||||
|
||||
# Migration only version of Gitlab::Access:BranchProtection application code.
|
||||
class BranchProtection
|
||||
attr_reader :level
|
||||
|
||||
def initialize(level)
|
||||
@level = level
|
||||
end
|
||||
|
||||
PROTECTION_NONE = 0
|
||||
PROTECTION_DEV_CAN_PUSH = 1
|
||||
PROTECTION_FULL = 2
|
||||
PROTECTION_DEV_CAN_MERGE = 3
|
||||
PROTECTION_DEV_CAN_INITIAL_PUSH = 4
|
||||
|
||||
DEVELOPER = 30
|
||||
MAINTAINER = 40
|
||||
|
||||
def to_hash
|
||||
case level
|
||||
when PROTECTION_NONE
|
||||
self.class.protection_none
|
||||
when PROTECTION_DEV_CAN_PUSH
|
||||
self.class.protection_partial
|
||||
when PROTECTION_FULL
|
||||
self.class.protected_fully
|
||||
when PROTECTION_DEV_CAN_MERGE
|
||||
self.class.protected_against_developer_pushes
|
||||
when PROTECTION_DEV_CAN_INITIAL_PUSH
|
||||
self.class.protected_after_initial_push
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def protection_none
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => Gitlab::Access::DEVELOPER }],
|
||||
allowed_to_merge: [{ 'access_level' => Gitlab::Access::DEVELOPER }],
|
||||
allow_force_push: true
|
||||
}
|
||||
end
|
||||
|
||||
def protection_partial
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => Gitlab::Access::DEVELOPER }],
|
||||
allowed_to_merge: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allow_force_push: false
|
||||
}
|
||||
end
|
||||
|
||||
def protected_fully
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allow_force_push: false
|
||||
}
|
||||
end
|
||||
|
||||
def protected_against_developer_pushes
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => Gitlab::Access::DEVELOPER }],
|
||||
allow_force_push: false
|
||||
}
|
||||
end
|
||||
|
||||
def protected_after_initial_push
|
||||
{
|
||||
allowed_to_push: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allowed_to_merge: [{ 'access_level' => Gitlab::Access::MAINTAINER }],
|
||||
allow_force_push: false,
|
||||
developer_can_initial_push: true
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
update_default_protection_branch_defaults(sub_batch)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_default_protection_branch_defaults(batch)
|
||||
namespace_settings = NamespaceSetting.where(namespace_id: batch.pluck(:namespace_id)).includes(:namespace)
|
||||
|
||||
values_list = namespace_settings.map do |namespace_setting|
|
||||
level = namespace_setting.namespace.default_branch_protection.to_i
|
||||
value = BranchProtection.new(level).to_hash.to_json
|
||||
"(#{namespace_setting.namespace_id}, '#{value}'::jsonb)"
|
||||
end.join(", ")
|
||||
|
||||
sql = <<~SQL
|
||||
WITH new_values (namespace_id, default_branch_protection_defaults) AS (
|
||||
VALUES
|
||||
#{values_list}
|
||||
)
|
||||
UPDATE namespace_settings
|
||||
SET default_branch_protection_defaults = new_values.default_branch_protection_defaults
|
||||
FROM new_values
|
||||
WHERE namespace_settings.namespace_id = new_values.namespace_id;
|
||||
SQL
|
||||
|
||||
connection.execute(sql)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -646,7 +646,8 @@ module QA
|
|||
def events(comments, label_events, state_events, milestone_events)
|
||||
mapped_label_events = label_events.map { |event| event_mapping["label_#{event[:action]}"] }
|
||||
mapped_milestone_events = milestone_events.map { |event| event_mapping["milestone_#{event[:action]}"] }
|
||||
mapped_state_event = state_events.map { |event| event[:state] }
|
||||
# merged events are fetched through comments so duplicates need to be removed
|
||||
mapped_state_event = state_events.map { |event| event[:state] }.reject { |state| state == "merged" }
|
||||
mapped_comment_events = comments.map do |c|
|
||||
event_mapping[c[:body].match(event_pattern)&.named_captures&.fetch("event", nil)]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -785,6 +785,13 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_
|
|||
ensure_no_tabs
|
||||
end
|
||||
|
||||
it 'renders logo', :js do
|
||||
visit new_user_session_path
|
||||
|
||||
image = find('img.js-portrait-logo-detection')
|
||||
expect(image['class']).to include('gl-h-9')
|
||||
end
|
||||
|
||||
it 'renders link to sign up path' do
|
||||
visit new_user_session_path
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import { initPortraitLogoDetection } from '~/logo';
|
||||
|
||||
describe('initPortraitLogoDetection', () => {
|
||||
let img;
|
||||
|
||||
const loadImage = () => {
|
||||
const loadEvent = new Event('load');
|
||||
img.dispatchEvent(loadEvent);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture('<img class="gl-visibility-hidden gl-h-9 js-portrait-logo-detection" />');
|
||||
initPortraitLogoDetection();
|
||||
img = document.querySelector('img');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetHTMLFixture();
|
||||
});
|
||||
|
||||
describe('when logo does not have portrait format', () => {
|
||||
beforeEach(() => {
|
||||
img.height = 10;
|
||||
img.width = 10;
|
||||
});
|
||||
|
||||
it('removes gl-visibility-hidden', () => {
|
||||
expect(img.classList).toContain('gl-visibility-hidden');
|
||||
expect(img.classList).toContain('gl-h-9');
|
||||
|
||||
loadImage();
|
||||
|
||||
expect(img.classList).not.toContain('gl-visibility-hidden');
|
||||
expect(img.classList).toContain('gl-h-9');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when logo has portrait format', () => {
|
||||
beforeEach(() => {
|
||||
img.height = 11;
|
||||
img.width = 10;
|
||||
});
|
||||
|
||||
it('removes gl-visibility-hidden', () => {
|
||||
expect(img.classList).toContain('gl-visibility-hidden');
|
||||
expect(img.classList).toContain('gl-h-9');
|
||||
|
||||
loadImage();
|
||||
|
||||
expect(img.classList).not.toContain('gl-visibility-hidden');
|
||||
expect(img.classList).toContain('gl-w-10');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillBranchProtectionNamespaceSetting,
|
||||
feature_category: :source_code_management do
|
||||
let(:namespaces_table) { table(:namespaces) }
|
||||
let(:namespace_settings_table) { table(:namespace_settings) }
|
||||
let(:group_namespace) do
|
||||
namespaces_table.create!(name: 'group_namespace', path: 'path-1', type: 'Group', default_branch_protection: 0)
|
||||
end
|
||||
|
||||
let(:user_namespace) do
|
||||
namespaces_table.create!(name: 'user_namespace', path: 'path-2', type: 'User', default_branch_protection: 1)
|
||||
end
|
||||
|
||||
let(:user_three_namespace) do
|
||||
namespaces_table.create!(name: 'user_three_namespace', path: 'path-3', type: 'User', default_branch_protection: 2)
|
||||
end
|
||||
|
||||
let(:group_four_namespace) do
|
||||
namespaces_table.create!(name: 'group_four_namespace', path: 'path-4', type: 'Group', default_branch_protection: 3)
|
||||
end
|
||||
|
||||
let(:group_five_namespace) do
|
||||
namespaces_table.create!(name: 'group_five_namespace', path: 'path-5', type: 'Group', default_branch_protection: 4)
|
||||
end
|
||||
|
||||
let(:start_id) { group_namespace.id }
|
||||
let(:end_id) { group_five_namespace.id }
|
||||
|
||||
subject(:perform_migration) do
|
||||
described_class.new(
|
||||
start_id: start_id,
|
||||
end_id: end_id,
|
||||
batch_table: :namespace_settings,
|
||||
batch_column: :namespace_id,
|
||||
sub_batch_size: 2,
|
||||
pause_ms: 0,
|
||||
connection: ActiveRecord::Base.connection
|
||||
).perform
|
||||
end
|
||||
|
||||
before do
|
||||
namespace_settings_table.create!(namespace_id: group_namespace.id, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: user_namespace.id, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: user_three_namespace.id, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: group_four_namespace.id, default_branch_protection_defaults: {})
|
||||
namespace_settings_table.create!(namespace_id: group_five_namespace.id, default_branch_protection_defaults: {})
|
||||
end
|
||||
|
||||
it 'updates default_branch_protection_defaults to a correct value', :aggregate_failures do
|
||||
expect(ActiveRecord::QueryRecorder.new { perform_migration }.count).to eq(16)
|
||||
|
||||
expect(migrated_attribute(group_namespace.id)).to eq({ "allow_force_push" => true,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 30 }] })
|
||||
expect(migrated_attribute(user_namespace.id)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 40 }],
|
||||
"allowed_to_push" => [{ "access_level" => 30 }] })
|
||||
expect(migrated_attribute(user_three_namespace.id)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 40 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }] })
|
||||
expect(migrated_attribute(group_four_namespace.id)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 30 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }] })
|
||||
expect(migrated_attribute(group_five_namespace.id)).to eq({ "allow_force_push" => false,
|
||||
"allowed_to_merge" => [{ "access_level" => 40 }],
|
||||
"allowed_to_push" => [{ "access_level" => 40 }],
|
||||
"developer_can_initial_push" => true })
|
||||
end
|
||||
|
||||
def migrated_attribute(namespace_id)
|
||||
namespace_settings_table.find(namespace_id).default_branch_protection_defaults
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillBranchProtectionNamespaceSetting, feature_category: :database do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :namespace_settings,
|
||||
column_name: :namespace_id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -462,37 +462,6 @@ RSpec.describe MergeRequestPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when enabling generate diff summary permission' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:mr) { create(:merge_request, target_project: project, source_project: project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let(:policy) { permissions(user, mr) }
|
||||
|
||||
context 'when can read_merge_request' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'allows to generate_diff_summary' do
|
||||
expect(policy).to be_allowed(:generate_diff_summary)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when can not read_merge_request' do
|
||||
it 'does not allow to generate_diff_summary' do
|
||||
expect(policy).not_to be_allowed(:generate_diff_summary)
|
||||
end
|
||||
|
||||
context 'and when is the LLM bot' do
|
||||
let(:user) { create(:user, :llm_bot) }
|
||||
|
||||
it 'allows to generate_diff_summary' do
|
||||
expect(policy).to be_allowed(:generate_diff_summary)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the author of the merge request is banned', feature_category: :insider_threat do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:admin) { create(:user, :admin) }
|
||||
|
|
|
|||
Loading…
Reference in New Issue