Commit e80e0dd6 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent ef31adeb
......@@ -141,7 +141,7 @@ export default {
data-qa-selector="file_name_link"
>
<i
v-if="!loadingPath"
v-if="path !== loadingPath"
:aria-label="type"
role="img"
:class="iconName"
......
......@@ -6,19 +6,25 @@ module Mutations
include Mutations::ResolvesProject
def resolve_issuable(type:, parent_path:, iid:)
parent = resolve_issuable_parent(parent_path)
parent = resolve_issuable_parent(type, parent_path)
issuable_resolver(type, parent, context).resolve(iid: iid.to_s)
end
private
def issuable_resolver(type, parent, context)
resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
resolver_class.single.new(object: parent, context: context, field: nil)
end
def resolve_issuable_parent(parent_path)
def resolve_issuable_parent(type, parent_path)
return unless type == :issue || type == :merge_request
resolve_project(full_path: parent_path)
end
end
end
Mutations::ResolvesIssuable.prepend_if_ee('::EE::Mutations::ResolvesIssuable')
......@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
VERSION = '0.14.0'
VERSION = '0.15.0'
self.table_name = 'clusters_applications_runners'
......
......@@ -468,6 +468,7 @@ class Member < ApplicationRecord
# for a Member to be commited before attempting to update the highest role.
# rubocop: disable CodeReuse/ServiceClass
def update_highest_role
return unless Feature.enabled?(:highest_role_callback)
return unless user_id.present?
return unless previous_changes[:access_level].present?
......
......@@ -217,6 +217,7 @@ class User < ApplicationRecord
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
before_validation :ensure_namespace_correct
before_save :ensure_namespace_correct # in case validation is skipped
before_save :ensure_bio_is_assigned_to_user_details, if: :bio_changed?
after_validation :set_username_errors
after_update :username_changed_hook, if: :saved_change_to_username?
after_destroy :post_destroy_hook
......@@ -1262,6 +1263,13 @@ class User < ApplicationRecord
end
end
# Temporary, will be removed when bio is fully migrated
def ensure_bio_is_assigned_to_user_details
return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true)
user_detail.bio = bio.to_s[0...255] # bio can be NULL in users, but cannot be NULL in user_details
end
def set_username_errors
namespace_path_errors = self.errors.delete(:"namespace.path")
self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
......
---
title: Add user_details.bio column and migrate data from users.bio
merge_request: 27773
author:
type: changed
---
title: Log member additions when importing Project/Group
merge_request: 27930
author:
type: other
---
title: Update GitLab Runner Helm Chart to 0.15.0
merge_request: 27670
author:
type: other
# frozen_string_literal: true
class AddBioToUserDetails < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:user_details, :bio, :string, default: '', allow_null: false, limit: 255, update_column_in_batches_args: { batch_column_name: :user_id })
end
def down
remove_column(:user_details, :bio)
end
end
# frozen_string_literal: true
class AddTempIndexOnUsersBio < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'tmp_idx_on_user_id_where_bio_is_filled'
disable_ddl_transaction!
def up
add_concurrent_index :users, :id, where: "(COALESCE(bio, '') IS DISTINCT FROM '')", name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :users, INDEX_NAME
end
end
# frozen_string_literal: true
class TriggerBackgroundMigrationForUsersBio < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INTERVAL = 2.minutes.to_i
BATCH_SIZE = 500
MIGRATION = 'MigrateUsersBioToUserDetails'
disable_ddl_transaction!
class User < ActiveRecord::Base
self.table_name = 'users'
include ::EachBatch
end
def up
relation = User.where("(COALESCE(bio, '') IS DISTINCT FROM '')")
queue_background_migration_jobs_by_range_at_intervals(relation,
MIGRATION,
INTERVAL,
batch_size: BATCH_SIZE)
end
def down
# no-op
end
end
......@@ -6152,7 +6152,8 @@ ALTER SEQUENCE public.user_custom_attributes_id_seq OWNED BY public.user_custom_
CREATE TABLE public.user_details (
user_id bigint NOT NULL,
job_title character varying(200) DEFAULT ''::character varying NOT NULL
job_title character varying(200) DEFAULT ''::character varying NOT NULL,
bio character varying(255) DEFAULT ''::character varying NOT NULL
);
CREATE SEQUENCE public.user_details_user_id_seq
......@@ -10243,6 +10244,8 @@ CREATE UNIQUE INDEX term_agreements_unique_index ON public.term_agreements USING
CREATE INDEX tmp_build_stage_position_index ON public.ci_builds USING btree (stage_id, stage_idx) WHERE (stage_idx IS NOT NULL);
CREATE INDEX tmp_idx_on_user_id_where_bio_is_filled ON public.users USING btree (id) WHERE ((COALESCE(bio, ''::character varying))::text IS DISTINCT FROM ''::text);
CREATE INDEX undefined_vulnerabilities ON public.vulnerability_occurrences USING btree (id) WHERE (severity = 0);
CREATE INDEX undefined_vulnerability ON public.vulnerabilities USING btree (id) WHERE (severity = 0);
......@@ -12797,7 +12800,10 @@ COPY "schema_migrations" (version) FROM STDIN;
20200319203901
20200320112455
20200320123839
20200323071918
20200323074147
20200323075043
20200323080714
20200323122201
20200323134519
20200324115359
......
......@@ -28,9 +28,7 @@ with Kubernetes.
## Requirements
Before installing GitLab, make sure to check the [requirements documentation](requirements.md)
which includes useful information on the supported Operating Systems as well as
the hardware requirements.
Before installing GitLab, it is of critical importance to review the system [requirements](requirements.md). The system requirements include details on the minimum hardware, software, database, and additional requirements to support GitLab.
## Installing GitLab using the Omnibus GitLab package (recommended)
......
......@@ -4,7 +4,8 @@ type: reference, how-to
# Sourcegraph integration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16556) in GitLab 12.5. Please note that this integration is in BETA and [behind a feature flag](#enable-the-sourcegraph-feature-flag).
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16556) in GitLab 12.5.
> - Note that this integration is in BETA and deployed [behind a feature flag](#enable-the-sourcegraph-feature-flag) disabled by default. Self-managed instances can opt to enable it.
[Sourcegraph](https://sourcegraph.com) provides code intelligence features, natively integrated into the GitLab UI.
......@@ -106,18 +107,17 @@ When visiting one of these views, you can now hover over a code reference to see
## Sourcegraph for GitLab.com
Sourcegraph powered code intelligence will be incrementally rolled out on GitLab.com.
It will eventually become available for all public projects, but for now, it is only
available for some specific projects within the [`gitlab-org`](https://gitlab.com/gitlab-org/)
group, e.g., <https://gitlab.com/gitlab-org/gitlab>. This means that you can see
it working and use it to dig into the code of these projects, but you cannot use
it on your own project on GitLab.com yet.
Sourcegraph powered code intelligence is avaialable for all public projects on GitLab.com.
If you would like to use it in your own projects as of GitLab 12.5, you can do so by
setting up a self-managed GitLab instance.
Support for private projects is currently not available for GitLab.com;
follow the epic [&2201](https://gitlab.com/groups/gitlab-org/-/epics/2201)
for updates.
Follow the epic [&2201](https://gitlab.com/groups/gitlab-org/-/epics/2201) for
updates.
## Troubleshooting
### Sourcegraph isn't working
If you enabled Sourcegraph for your project but still it doesn't looklike it's working, it might be because Sourcegraph has not indexed theproject yet. You can check for Sourcegraph's availability of your project by visiting `https://sourcegraph.com/gitlab.com/<project-path>`replacing `<project-path>` with the path to your GitLab project.
## Sourcegraph and Privacy
......
......@@ -168,6 +168,9 @@ Please see the table below for some examples:
| 12.5.8 | 11.3.4 | `11.3.4` -> `11.11.8` -> `12.0.12` -> `12.5.8` | `11.11.8` is the last version in version `11`. `12.0.x` [is a required step](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23211#note_272842444). |
| 12.8.5 | 9.2.6 | `9.2.6` -> `9.5.10` -> `10.8.7` -> `11.11.8` -> `12.0.12` -> `12.8.5` | Four intermediate versions are required: the final 9.5, 10.8, 11.11 releases, plus 12.0. |
NOTE: **Note:**
Instructions for installing a specific version of GitLab or downloading the package locally for installation can be found at [GitLab Repositories](https://packages.gitlab.com/gitlab).
## More information
More information about the release procedures can be found in our
......
......@@ -96,7 +96,9 @@ GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API
fleet. You can expect connections from webhooks or repository mirroring to come
from those IPs and whitelist them.
For connections from CI/CD runners we are not providing static IP addresses.
GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com you might need to whitelist CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4) and [IPv6](https://www.cloudflare.com/ips-v6))
For outgoing connections from CI/CD runners we are not providing static IP addresses.
All our runners are deployed into Google Cloud Platform (GCP) - any IP based
firewall can be configured by looking up all
[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#where_can_i_find_product_name_short_ip_ranges).
......
......@@ -155,6 +155,26 @@ and the target project and branch where you want to merge the changes into.
Click on **Compare branches and continue** to go to the
[**New Merge Request** page](#new-merge-request-page) and fill in the details.
## New merge request from a fork
After forking a project and applying your local changes, complete the following steps to
create a merge request from your fork to contribute back to the main project:
1. Go to **Projects > Your Projects** and select your fork of the repository.
1. In the left menu, go to **Merge Requests**, and click **New Merge Request**.
1. In the **Source branch** drop-down list box, select your branch in your forked repository as the source branch.
1. In the **Target branch** drop-down list box, select the branch from the upstream repository as the target branch.
1. After entering the credentials, click **Compare branches and continue** to compare your local changes to the upstream repository.
1. Assign a user to review your changes, and click **Submit merge request**.
When the changes are merged, your changes are added to the upstream repository and
the branch as per specification. After your work is merged, if you don't intend to
make any other contributions to the upstream project, you can unlink your
fork from its upstream project in the **Settings > Advanced Settings** section by
[removing the forking relashionship](../settings/index.md#removing-a-fork-relationship).
For further details, [see the forking workflow documentation](../repository/forking_workflow.md).
## New merge request by email **(CORE ONLY)**
_This feature needs [incoming email](../../../administration/incoming_email.md)
......
# frozen_string_literal: true
# rubocop:disable Style/Documentation
module Gitlab
module BackgroundMigration
class MigrateUsersBioToUserDetails
class User < ActiveRecord::Base
self.table_name = 'users'
end
class UserDetails < ActiveRecord::Base
self.table_name = 'user_details'
end
def perform(start_id, stop_id)
return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true)
relation = User
.select("id AS user_id", "substring(COALESCE(bio, '') from 1 for 255) AS bio")
.where("(COALESCE(bio, '') IS DISTINCT FROM '')")
.where(id: (start_id..stop_id))
ActiveRecord::Base.connection.execute <<-EOF.strip_heredoc
INSERT INTO user_details
(user_id, bio)
#{relation.to_sql}
ON CONFLICT (user_id)
DO UPDATE SET
"bio" = EXCLUDED."bio";
EOF
end
end
end
end
......@@ -375,8 +375,11 @@ module Gitlab
# less "complex" without introducing extra methods (which actually will
# make things _more_ complex).
#
# `batch_column_name` option is for tables without primary key, in this
# case an other unique integer column can be used. Example: :user_id
#
# rubocop: disable Metrics/AbcSize
def update_column_in_batches(table, column, value, batch_size: nil)
def update_column_in_batches(table, column, value, batch_size: nil, batch_column_name: :id)
if transaction_open?
raise 'update_column_in_batches can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \
......@@ -403,14 +406,14 @@ module Gitlab
batch_size = max_size if batch_size > max_size
end
start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
start_arel = table.project(table[batch_column_name]).order(table[batch_column_name].asc).take(1)
start_arel = yield table, start_arel if block_given?
start_id = exec_query(start_arel.to_sql).to_a.first['id'].to_i
start_id = exec_query(start_arel.to_sql).to_a.first[batch_column_name.to_s].to_i
loop do
stop_arel = table.project(table[:id])
.where(table[:id].gteq(start_id))
.order(table[:id].asc)
stop_arel = table.project(table[batch_column_name])
.where(table[batch_column_name].gteq(start_id))
.order(table[batch_column_name].asc)
.take(1)
.skip(batch_size)
......@@ -420,12 +423,12 @@ module Gitlab
update_arel = Arel::UpdateManager.new
.table(table)
.set([[table[column], value]])
.where(table[:id].gteq(start_id))
.where(table[batch_column_name].gteq(start_id))
if stop_row
stop_id = stop_row['id'].to_i
stop_id = stop_row[batch_column_name.to_s].to_i
start_id = stop_id
update_arel = update_arel.where(table[:id].lt(stop_id))
update_arel = update_arel.where(table[batch_column_name].lt(stop_id))
end
update_arel = yield table, update_arel if block_given?
......@@ -461,7 +464,7 @@ module Gitlab
#
# This method can also take a block which is passed directly to the
# `update_column_in_batches` method.
def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, update_column_in_batches_args: {}, &block)
if transaction_open?
raise 'add_column_with_default can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \
......@@ -483,7 +486,12 @@ module Gitlab
begin
default_after_type_cast = connection.type_cast(default, column_for(table, column))
update_column_in_batches(table, column, default_after_type_cast, &block)
if update_column_in_batches_args.any?
update_column_in_batches(table, column, default_after_type_cast, **update_column_in_batches_args, &block)
else
update_column_in_batches(table, column, default_after_type_cast, &block)
end
change_column_null(table, column, false) unless allow_null
# We want to rescue _all_ exceptions here, even those that don't inherit
......
......@@ -64,8 +64,19 @@ module Gitlab
def add_team_member(member, existing_user = nil)
member['user'] = existing_user
member_hash = member_hash(member)
relation_class.create(member_hash(member)).persisted?
member = relation_class.create(member_hash)
if member.persisted?
log_member_addition(member_hash)
true
else
log_member_addition_failure(member_hash, member.errors.full_messages)
false
end
end
def member_hash(member)
......@@ -103,6 +114,34 @@ module Gitlab
relation_class::MAINTAINER
end
def log_member_addition(member_hash)
log_params = base_log_params(member_hash)
log_params[:message] = '[Project/Group Import] Added new member'
logger.info(log_params)
end
def log_member_addition_failure(member_hash, errors)
log_params = base_log_params(member_hash)
log_params[:message] = "[Project/Group Import] Member addition failed: #{errors&.join(', ')}"
logger.info(log_params)
end
def base_log_params(member_hash)
{
user_id: member_hash['user']&.id,
access_level: member_hash['access_level'],
importable_type: @importable.class.to_s,
importable_id: @importable.id,
root_namespace_id: @importable.try(:root_ancestor)&.id
}
end
def logger
@logger ||= Gitlab::Import::Logger.build
end
end
end
end
......@@ -3,76 +3,27 @@
require 'spec_helper'
describe Mutations::ResolvesIssuable do
let(:mutation_class) do
let_it_be(:mutation_class) do
Class.new(Mutations::BaseMutation) do
include Mutations::ResolvesIssuable
end
end
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:context) { { current_user: user } }
let(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
shared_examples 'resolving an issuable' do |type|
context 'when user has access' do
let(:source) { type == :merge_request ? 'source_project' : 'project' }
let(:issuable) { create(type, author: user, "#{source}" => project) }
subject { mutation.resolve_issuable(type: type, parent_path: project.full_path, iid: issuable.iid) }
before do
project.add_developer(user)
end
it 'resolves issuable by iid' do
result = type == :merge_request ? subject.sync : subject
expect(result).to eq(issuable)
end
it 'uses the correct Resolver to resolve issuable' do
resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
resolved_project = mutation.resolve_project(full_path: project.full_path)
allow(mutation).to receive(:resolve_project)
.with(full_path: project.full_path)
.and_return(resolved_project)
expect(resolver_class).to receive(:new)
.with(object: resolved_project, context: context, field: nil)
.and_call_original
subject
end
it 'uses the ResolvesProject to resolve project' do
expect(Resolvers::ProjectResolver).to receive(:new)
.with(object: nil, context: context, field: nil)
.and_call_original
subject
end
it 'returns nil if issuable is not found' do
result = mutation.resolve_issuable(type: type, parent_path: project.full_path, iid: "100")
result = type == :merge_request ? result.sync : result
expect(result).to be_nil
end
it 'returns nil if parent path is empty' do
result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
expect(result).to be_nil
end
end
end
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:context) { { current_user: user } }
let_it_be(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
let(:parent) { issuable.project }
context 'with issues' do
it_behaves_like 'resolving an issuable', :issue
let(:issuable) { create(:issue, project: project) }
it_behaves_like 'resolving an issuable in GraphQL', :issue
end
context 'with merge requests' do
it_behaves_like 'resolving an issuable', :merge_request
let(:issuable) { create(:merge_request, source_project: project) }
it_behaves_like 'resolving an issuable in GraphQL', :merge_request
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migration, schema: 20200323074147 do
let(:users) { table(:users) }
let(:user_details) do
klass = table(:user_details)
klass.primary_key = :user_id
klass
end
let!(:user_needs_migration) { users.create(name: 'user1', email: 'test1@test.com', projects_limit: 1, bio: 'bio') }
let!(:user_needs_no_migration) { users.create(name: 'user2', email: 'test2@test.com', projects_limit: 1) }
let!(:user_also_needs_no_migration) { users.create(name: 'user3', email: 'test3@test.com', projects_limit: 1, bio: '') }
let!(:user_with_long_bio) { users.create(name: 'user4', email: 'test4@test.com', projects_limit: 1, bio: 'a' * 256) } # 255 is the max
let!(:user_already_has_details) { users.create(name: 'user5', email: 'test5@test.com', projects_limit: 1, bio: 'my bio') }
let!(:existing_user_details) { user_details.find_or_create_by(user_id: user_already_has_details.id).update(bio: 'my bio') }
# unlikely scenario since we have triggers
let!(:user_has_different_details) { users.create(name: 'user6', email: 'test6@test.com', projects_limit: 1, bio: 'different') }
let!(:different_existing_user_details) { user_details.find_or_create_by(user_id: user_has_different_details.id).update(bio: 'bio') }
let(:user_ids) do
[
user_needs_migration,
user_needs_no_migration,
user_also_needs_no_migration,
user_with_long_bio,
user_already_has_details,
user_has_different_details
].map(&:id)
end
subject { described_class.new.perform(user_ids.min, user_ids.max) }
it 'migrates all relevant records' do
subject
all_user_details = user_details.all
expect(all_user_details.size).to eq(4)
end
it 'migrates `bio`' do
subject
user_detail = user_details.find_by!(user_id: user_needs_migration.id)
expect(user_detail.bio).to eq('bio')
end
it 'migrates long `bio`' do
subject
user_detail = user_details.find_by!(user_id: user_with_long_bio.id)
expect(user_detail.bio).to eq('a' * 255)
end
it 'does not change existing user detail' do
expect { subject }.not_to change { user_details.find_by!(user_id: user_already_has_details.id).attributes }
end
it 'changes existing user detail when the columns are different' do
expect { subject }.to change { user_details.find_by!(user_id: user_has_different_details.id).bio }.from('bio').to('different')
end
it 'does not migrate record' do
subject
user_detail = user_details.find_by(user_id: user_needs_no_migration.id)
expect(user_detail).to be_nil
end
it 'does not migrate empty bio' do
subject
user_detail = user_details.find_by(user_id: user_also_needs_no_migration.id)
expect(user_detail).to be_nil
end
context 'when `migrate_bio_to_user_details` feature flag is off' do
before do
stub_feature_flags(migrate_bio_to_user_details: false)
end
it 'does nothing' do
already_existing_user_details = user_details.where(user_id: [
user_has_different_details.id,
user_already_has_details.id
])
subject
expect(user_details.all).to match_array(already_existing_user_details)
end
end
end
......@@ -657,6 +657,30 @@ describe Gitlab::Database::MigrationHelpers do
end
end
context 'when `update_column_in_batches_args` is given' do
let(:column) { UserDetail.columns.find { |c| c.name == "user_id" } }
it 'uses `user_id` for `update_column_in_batches`' do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:transaction).and_yield
allow(model).to receive(:column_for).with(:user_details, :foo).and_return(column)
allow(model).to receive(:update_column_in_batches).with(:user_details, :foo, 10, batch_column_name: :user_id)
allow(model).to receive(:change_column_null).with(:user_details, :foo, false)
allow(model).to receive(:change_column_default).with(:user_details, :foo, 10)
expect(model).to receive(:add_column)
.with(:user_details, :foo, :integer, default: nil)
model.add_column_with_default(
:user_details,
:foo,
:integer,
default: 10,
update_column_in_batches_args: { batch_column_name: :user_id }
)
end
end
context 'when a column limit is set' do
it 'adds the column with a limit' do
allow(model).to receive(:transaction_open?).and_return(false)
......
......@@ -78,6 +78,66 @@ describe Gitlab::ImportExport::MembersMapper do
members_mapper.map
end
context 'logging' do
let(:logger) { Gitlab::Import::Logger.build }
before do
allow(logger).to receive(:info)
allow(members_mapper).to receive(:logger).and_return(logger)
end
it 'logs member addition' do
expected_log_params = ->(user_id) do
{
user_id: user_id,
root_namespace_id: importable.root_ancestor.id,
importable_type: importable.class.to_s,
importable_id: importable.id,
access_level: exported_members.first['access_level'],
message: '[Project/Group Import] Added new member'
}
end
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(user2.id))).once
expect(logger).to receive(:info).with(hash_including(expected_log_params.call(nil))).once
members_mapper.map
end
context 'when exporter member is invalid' do
let(:exported_members) do
[
{
"id" => 2,
"access_level" => -5, # invalid access level
"source_id" => 14,
"source_type" => source_type,
"notification_level" => 3,
"created_at" => "2016-03-11T10:21:44.822Z",
"updated_at" => "2016-03-11T10:21:44.822Z",
"created_by_id" => nil,
"invite_email" => nil,
"invite_token" => nil,
"invite_accepted_at" => nil,
"user" =>
{
"id" => exported_user_id,
"email" => user2.email,
"username" => 'test'
},
"user_id" => 19
}
]
end
it 'logs member addition failure' do
expect(logger).to receive(:info).with(hash_including(message: a_string_including('Access level is not included in the list'))).once
members_mapper.map
end
end
end
context 'user is not an admin' do
let(:user) { create(:user) }
......
......@@ -586,49 +586,97 @@ describe Member do
end
context 'when after_commit :update_highest_role' do
where(:member_type, :source_type) do
:project_member | :project
:group_member | :group
end
context 'with feature flag enabled' do
where(:member_type, :source_type) do
:project_member | :project
:group_member | :group
end
with_them do
describe 'create member' do
it 'initializes a new Members::UpdateHighestRoleService object' do
source = create(source_type) # source owner initializes a new service object too
user = create(:user)
with_them do
describe 'create member' do
it 'initializes a new Members::UpdateHighestRoleService object' do
source = create(source_type) # source owner initializes a new service object too
user = create(:user)
expect(Members::UpdateHighestRoleService).to receive(:new).with(user.id).and_call_original
expect(Members::UpdateHighestRoleService).to receive(:new).with(user.id).and_call_original
create(member_type, :guest, user: user, source_type => source)
create(member_type, :guest, user: user, source_type => source)
end
end
end
context 'when member exists' do
let!(:member) { create(member_type) }
context 'when member exists' do
let!(:member) { create(member_type) }
describe 'update member' do
context 'when access level was changed' do
it 'initializes a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).to receive(:new).with(member.user_id).and_call_original
member.update(access_level: Gitlab::Access::GUEST)
end
end
context 'when access level was not changed' do
it 'does not initialize a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).not_to receive(:new).with(member.user_id)
member.update(notification_level: NotificationSetting.levels[:disabled])
end
end
end
describe 'update member' do
context 'when access level was changed' do
describe 'destroy member' do
it 'initializes a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).to receive(:new).with(member.user_id).and_call_original
member.update(access_level: Gitlab::Access::GUEST)
member.destroy
end
end
end
end
end
context 'when access level was not changed' do
it 'does not initialize a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).not_to receive(:new).with(member.user_id)
context 'with feature flag disabled' do
before do
stub_feature_flags(highest_role_callback: false)
end
member.update(notification_level: NotificationSetting.levels[:disabled])
end
where(:member_type, :source_type) do
:project_member | :project
:group_member | :group
end
with_them do
describe 'create member' do
it 'does not initialize a new Members::UpdateHighestRoleService object' do
source = create(source_type)
user = create(:user)
expect(Members::UpdateHighestRoleService).not_to receive(:new).with(user.id)
create(member_type, :guest, user: user, source_type => source)
end
end
describe 'destroy member' do
it 'initializes a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).to receive(:new).with(member.user_id).and_call_original
context 'when member exists' do
let!(:member) { create(member_type) }
member.destroy
describe 'update member' do
context 'when access level was changed' do
it 'does not initialize a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).not_to receive(:new).with(member.user_id)
member.update(access_level: Gitlab::Access::GUEST)
end
end
end
describe 'destroy member' do
it 'does not initialize a new Members::UpdateHighestRoleService object' do
expect(Members::UpdateHighestRoleService).not_to receive(:new).with(member.user_id)
member.destroy
end
end
end
end
......
......@@ -55,6 +55,67 @@ describe User, :do_not_mock_admin_mode do
it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') }
it { is_expected.to have_many(:releases).dependent(:nullify) }
describe "#bio" do
it 'syncs bio with `user_details.bio` on create' do
user = create(:user, bio: 'my bio')
expect(user.bio).to eq(user.user_detail.bio)
end
context 'when `migrate_bio_to_user_details` feature flag is off' do
before do
stub_feature_flags(migrate_bio_to_user_details: false)
end
it 'does not sync bio with `user_details.bio`' do
user = create(:user, bio: 'my bio')
expect(user.bio).to eq('my bio')
expect(user.user_detail.bio).to eq('')
end
end
it 'syncs bio with `user_details.bio` on update' do
user = create(:user)
user.update!(bio: 'my bio')
expect(user.bio).to eq(user.user_detail.bio)
end
context 'when `user_details` association already exists' do
let(:user) { create(:user) }
before do
create(:user_detail, user: user)
end
it 'syncs bio with `user_details.bio`' do
user.update!(bio: 'my bio')
expect(user.bio).to eq(user.user_detail.bio)
end
it 'falls back to "" when nil is given' do
user.update!(bio: nil)
expect(user.bio).to eq(nil)
expect(user.user_detail.bio).to eq('')
end
# very unlikely scenario
it 'truncates long bio when syncing to user_details' do
invalid_bio = 'a' * 256
truncated_bio = 'a' * 255
user.bio = invalid_bio
user.save(validate: false)
expect(user.user_detail.bio).to eq(truncated_bio)
end
end
end
describe "#abuse_report" do
let(:current_user) { create(:user) }
let(:other_user) { create(:user) }
......
......@@ -739,6 +739,17 @@ describe API::Users, :do_not_mock_admin_mode do
expect(user.reload.bio).to eq('new test bio')
end
it "updates user with empty bio" do
user.bio = 'previous bio'
user.save!
put api("/users/#{user.id}", admin), params: { bio: '' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['bio']).to eq('')
expect(user.reload.bio).to eq('')
end
it "updates user with new password and forces reset on next login" do
put api("/users/#{user.id}", admin), params: { password: '12345678' }
......
# frozen_string_literal: true
RSpec.shared_examples 'resolving an issuable in GraphQL' do |type|
subject { mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: issuable.iid) }
context 'when user has access' do
before do
parent.add_developer(user)
end
it 'resolves issuable by iid' do
result = type == :merge_request ? subject.sync : subject
expect(result).to eq(issuable)
end
it 'uses the correct Resolver to resolve issuable' do
resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize
resolve_method = type == :epic ? :resolve_group : :resolve_project
resolved_parent = mutation.send(resolve_method, full_path: parent.full_path)
allow(mutation).to receive(resolve_method)
.with(full_path: parent.full_path)
.and_return(resolved_parent)
expect(resolver_class).to receive(:new)
.with(object: resolved_parent, context: context, field: nil)
.and_call_original
subject
end
it 'uses correct Resolver to resolve issuable parent' do
resolver_class = type == :epic ? 'Resolvers::GroupResolver' : 'Resolvers::ProjectResolver'
expect(resolver_class.constantize).to receive(:new)
.with(object: nil, context: context, field: nil)
.and_call_original
subject
end
it 'returns nil if issuable is not found' do
result = mutation.resolve_issuable(type: type, parent_path: parent.full_path, iid: "100")
result = type == :merge_request ? result.sync : result
expect(result).to be_nil
end
it 'returns nil if parent path is not present' do
result = mutation.resolve_issuable(type: type, parent_path: "", iid: issuable.iid)
expect(result).to be_nil
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment