Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1f5225c80c
commit
1343550892
|
|
@ -538,6 +538,7 @@ Gitlab/RSpec/AvoidSetup:
|
|||
- 'ee/spec/features/registrations/saas/**/*'
|
||||
- 'ee/spec/features/trials/saas/**/*'
|
||||
- 'ee/spec/features/gitlab_subscriptions/trials/duo_pro/**/*'
|
||||
- 'ee/spec/features/gitlab_subscriptions/trials/duo_enterprise/**/*'
|
||||
|
||||
RSpec/DuplicateSpecLocation:
|
||||
Enabled: true
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ Layout/LineEndStringConcatenationIndentation:
|
|||
- 'ee/app/components/namespaces/storage/user_pre_enforcement_alert_component.rb'
|
||||
- 'ee/app/controllers/concerns/insights_actions.rb'
|
||||
- 'ee/app/controllers/ee/ldap/omniauth_callbacks_controller.rb'
|
||||
- 'ee/app/controllers/gitlab_subscriptions/trials/duo_pro_controller.rb'
|
||||
- 'ee/app/finders/geo/framework_registry_finder.rb'
|
||||
- 'ee/app/graphql/ee/mutations/ci/project_ci_cd_settings_update.rb'
|
||||
- 'ee/app/graphql/ee/mutations/issues/create.rb'
|
||||
|
|
|
|||
|
|
@ -335,7 +335,6 @@ Rails/StrongParams:
|
|||
- 'ee/app/controllers/smartcard_controller.rb'
|
||||
- 'ee/app/controllers/subscriptions/groups_controller.rb'
|
||||
- 'ee/app/controllers/subscriptions/hand_raise_leads_controller.rb'
|
||||
- 'ee/app/controllers/gitlab_subscriptions/trials/duo_pro_controller.rb'
|
||||
- 'ee/app/controllers/subscriptions/trials_controller.rb'
|
||||
- 'ee/app/controllers/subscriptions_controller.rb'
|
||||
- 'ee/app/controllers/users/base_identity_verification_controller.rb'
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -626,7 +626,7 @@ gem 'ssh_data', '~> 1.3' # rubocop:todo Gemfile/MissingFeatureCategory
|
|||
gem 'spamcheck', '~> 1.3.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
# Gitaly GRPC protocol definitions
|
||||
gem 'gitaly', '~> 17.2.0', feature_category: :gitaly
|
||||
gem 'gitaly', '~> 17.4.0.pre.rc1', feature_category: :gitaly
|
||||
|
||||
# KAS GRPC protocol definitions
|
||||
gem 'gitlab-kas-grpc', '~> 17.4.0.pre.rc1', feature_category: :deployment_management
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@
|
|||
{"name":"gettext","version":"3.4.9","platform":"ruby","checksum":"292864fe6a15c224cee4125a4a72fab426fdbb280e4cff3cfe44935f549b009a"},
|
||||
{"name":"gettext_i18n_rails","version":"1.12.0","platform":"ruby","checksum":"6ac4817731a9e2ce47e1e83381ac34f9142263bc2911aaaafb2526d2f1afc1be"},
|
||||
{"name":"git","version":"1.18.0","platform":"ruby","checksum":"c9b80462e4565cd3d7a9ba8440c41d2c52244b17b0dad0bfddb46de70630c465"},
|
||||
{"name":"gitaly","version":"17.2.0","platform":"ruby","checksum":"48eee8883c43bb2f8fedbb43e4543439cfe37c33becebaec9ea1d425f9cce865"},
|
||||
{"name":"gitaly","version":"17.4.0.pre.rc1","platform":"ruby","checksum":"72c69dfa77871be78dd2e017be3131b9515b396d779df5785f77df291219f7b3"},
|
||||
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
|
||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||
{"name":"gitlab-dangerfiles","version":"4.8.0","platform":"ruby","checksum":"b327d079552ec974a63bf34d749a0308425af6ebf51d01064f1a6ff216a523db"},
|
||||
|
|
|
|||
|
|
@ -701,7 +701,7 @@ GEM
|
|||
git (1.18.0)
|
||||
addressable (~> 2.8)
|
||||
rchardet (~> 1.8)
|
||||
gitaly (17.2.0)
|
||||
gitaly (17.4.0.pre.rc1)
|
||||
grpc (~> 1.0)
|
||||
gitlab (4.19.0)
|
||||
httparty (~> 0.20)
|
||||
|
|
@ -2053,7 +2053,7 @@ DEPENDENCIES
|
|||
gdk-toogle (~> 0.9, >= 0.9.5)
|
||||
gettext (~> 3.4, >= 3.4.9)
|
||||
gettext_i18n_rails (~> 1.12.0)
|
||||
gitaly (~> 17.2.0)
|
||||
gitaly (~> 17.4.0.pre.rc1)
|
||||
gitlab-backup-cli!
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 4.8.0)
|
||||
|
|
|
|||
|
|
@ -259,6 +259,10 @@
|
|||
"markdownDescription": "Reports will be uploaded as artifacts, and often displayed in the Gitlab UI, such as in merge requests. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#artifactsreports).",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "string",
|
||||
"description": "Path to JSON file with annotations report."
|
||||
},
|
||||
"junit": {
|
||||
"description": "Path for file(s) that should be parsed as JUnit XML result",
|
||||
"oneOf": [
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ class UsersController < ApplicationController
|
|||
|
||||
def exists
|
||||
if Gitlab::CurrentSettings.signup_enabled? || current_user
|
||||
render json: { exists: !!Namespace.without_project_namespaces.find_by_path_or_name(params[:username]) }
|
||||
render json: { exists: Namespace.username_reserved?(params[:username]) }
|
||||
else
|
||||
render json: { error: _('You must be authenticated to access this path.') }, status: :unauthorized
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,5 +37,10 @@ module Namespaces
|
|||
|
||||
groups.by_min_access_level(current_user, params[:min_access_level])
|
||||
end
|
||||
|
||||
def filter_groups(groups)
|
||||
by_search(groups)
|
||||
.then { |filtered_groups| by_min_access_level(filtered_groups) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
# group
|
||||
# current_user
|
||||
# params:
|
||||
# relations: string
|
||||
# relation: string - groups by relation (direct or inherited)
|
||||
# search: string
|
||||
# min_access_level: integer
|
||||
#
|
||||
|
|
@ -18,8 +18,6 @@ module Namespaces
|
|||
include Namespaces::GroupsFilter
|
||||
include Gitlab::Allowable
|
||||
|
||||
attr_reader :group, :current_user, :params
|
||||
|
||||
def initialize(group, current_user = nil, params = {})
|
||||
@group = group
|
||||
@current_user = current_user
|
||||
|
|
@ -31,19 +29,16 @@ module Namespaces
|
|||
|
||||
group_links = group_group_links(group, include_relations)
|
||||
groups = Group.id_in(group_links.select(:shared_with_group_id)).public_or_visible_to_user(current_user)
|
||||
groups = filter_invited_groups(groups)
|
||||
groups = filter_groups(groups)
|
||||
sort(groups).with_route
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_invited_groups(groups)
|
||||
by_search(groups)
|
||||
.then { |filtered_groups| by_min_access_level(filtered_groups) }
|
||||
end
|
||||
attr_reader :group, :current_user, :params
|
||||
|
||||
def include_relations
|
||||
[params[:relation].try(:to_sym)]
|
||||
Array(params[:relation]).map(&:to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Projects::InvitedGroupsFinder
|
||||
#
|
||||
# Used to get the list of invited groups in the given project
|
||||
# Arguments:
|
||||
# group
|
||||
# current_user
|
||||
# params:
|
||||
# relation: string - groups by relation (direct or inherited)
|
||||
# search: string
|
||||
# min_access_level: integer
|
||||
#
|
||||
module Namespaces
|
||||
module Projects
|
||||
class InvitedGroupsFinder
|
||||
include Namespaces::GroupsFilter
|
||||
include Gitlab::Allowable
|
||||
|
||||
def initialize(project, current_user = nil, params = {})
|
||||
@project = project
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
return Group.none unless can?(current_user, :read_project, project)
|
||||
|
||||
groups = group_links(include_relations).public_or_visible_to_user(current_user)
|
||||
groups = filter_groups(groups)
|
||||
sort(groups).with_route
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :current_user, :params
|
||||
|
||||
def include_relations
|
||||
Array(params[:relation]).map(&:to_sym)
|
||||
end
|
||||
|
||||
def group_links(include_relations)
|
||||
case include_relations
|
||||
when [:direct]
|
||||
direct
|
||||
when [:inherited]
|
||||
inherited
|
||||
else
|
||||
Group.from_union(direct, inherited)
|
||||
end
|
||||
end
|
||||
|
||||
def direct
|
||||
Group.id_in(project.project_group_links.select(:group_id))
|
||||
end
|
||||
|
||||
def inherited
|
||||
Group.id_in(project.group_group_links.distinct_on_shared_with_group_id_with_group_access
|
||||
.select(:shared_with_group_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Namespaces::Projects::InvitedGroupsFinder.prepend_mod
|
||||
|
|
@ -513,6 +513,7 @@ module ApplicationSettingsHelper
|
|||
:group_projects_api_limit,
|
||||
:groups_api_limit,
|
||||
:project_api_limit,
|
||||
:project_invited_groups_api_limit,
|
||||
:projects_api_limit,
|
||||
:user_contributed_projects_api_limit,
|
||||
:user_projects_api_limit,
|
||||
|
|
|
|||
|
|
@ -598,6 +598,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
:packages_cleanup_package_file_worker_capacity,
|
||||
:pipeline_limit_per_project_user_sha,
|
||||
:project_api_limit,
|
||||
:project_invited_groups_api_limit,
|
||||
:projects_api_limit,
|
||||
:projects_api_rate_limit_unauthenticated,
|
||||
:raw_blob_request_limit,
|
||||
|
|
@ -624,6 +625,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
groups_api_limit: [:integer, { default: 200 }],
|
||||
members_delete_limit: [:integer, { default: 60 }],
|
||||
project_api_limit: [:integer, { default: 400 }],
|
||||
project_invited_groups_api_limit: [:integer, { default: 60 }],
|
||||
projects_api_limit: [:integer, { default: 2000 }],
|
||||
user_contributed_projects_api_limit: [:integer, { default: 100 }],
|
||||
user_projects_api_limit: [:integer, { default: 300 }],
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ module ApplicationSettingImplementation
|
|||
group_shared_groups_api_limit: 60,
|
||||
groups_api_limit: 200,
|
||||
project_api_limit: 400,
|
||||
project_invited_groups_api_limit: 60,
|
||||
projects_api_limit: 2000,
|
||||
user_contributed_projects_api_limit: 100,
|
||||
user_projects_api_limit: 300,
|
||||
|
|
|
|||
|
|
@ -362,6 +362,10 @@ class Namespace < ApplicationRecord
|
|||
ensure
|
||||
Gitlab::SafeRequestStore[:require_organization] = current_value
|
||||
end
|
||||
|
||||
def username_reserved?(username)
|
||||
without_project_namespaces.where(parent_id: nil).find_by_path_or_name(username).present?
|
||||
end
|
||||
end
|
||||
|
||||
def to_reference_base(from = nil, full: false, absolute_path: false)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@
|
|||
"minimum": 0,
|
||||
"description": "Number of requests allowed to the GET /api/v4/projects/:id API."
|
||||
},
|
||||
"project_invited_groups_api_limit": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Number of requests allowed to the GET /api/v4/projects/:id/invited_groups API."
|
||||
},
|
||||
"projects_api_limit": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
|
|
|
|||
|
|
@ -42,4 +42,9 @@
|
|||
= f.label :user_starred_projects_api_limit, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/starred_projects', timeframe: 'minute'), class: 'label-bold'
|
||||
= f.number_field :user_starred_projects_api_limit, min: 0, class: 'form-control gl-form-input'
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :project_invited_groups_api_limit, format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /projects/:id/invited_groups', timeframe: 'minute'), class: 'label-bold'
|
||||
= f.number_field :project_invited_groups_api_limit, min: 0, class: 'form-control gl-form-input'
|
||||
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: summarize_notes_with_anthropic
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134731
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/work_items/430196
|
||||
milestone: '16.6'
|
||||
type: development
|
||||
group: group::duo chat
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
migration_job_name: CopyTaggingsToPCiBuildTags
|
||||
description: Move jobs data from taggings into p_ci_build_tags
|
||||
feature_category: continuous_integration
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162706
|
||||
milestone: '17.4'
|
||||
queued_migration_version: 20240814075849
|
||||
finalize_after: '2024-10-15'
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
migration_job_name: RerunEpicDatesToWorkItemDatesSourcesSync
|
||||
description: >
|
||||
We backfilled work_item_dates_sources with epic dates data in 17.1,
|
||||
but we now need to re-do the migration due to a fix in the syncing mechanism in 17.4.
|
||||
feature_category: team_planning
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162876
|
||||
milestone: '17.4'
|
||||
queued_migration_version: 20240816110844
|
||||
finalize_after: '2024-08-20'
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueCopyTaggingsToPCiBuildTags < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_ci
|
||||
|
||||
MIGRATION = "CopyTaggingsToPCiBuildTags"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 25000
|
||||
SUB_BATCH_SIZE = 150
|
||||
GITLAB_OPTIMIZED_BATCH_SIZE = 75_000
|
||||
GITLAB_OPTIMIZED_SUB_BATCH_SIZE = 250
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:taggings,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
**batch_sizes
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :taggings, :id, [])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def batch_sizes
|
||||
if Gitlab.com_except_jh?
|
||||
{
|
||||
batch_size: GITLAB_OPTIMIZED_BATCH_SIZE,
|
||||
sub_batch_size: GITLAB_OPTIMIZED_SUB_BATCH_SIZE
|
||||
}
|
||||
else
|
||||
{
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueRerunEpicDatesToWorkItemDatesSourcesSync < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.4'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = "RerunEpicDatesToWorkItemDatesSourcesSync"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 500
|
||||
SUB_BATCH_SIZE = 10
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:epics,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :epics, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
374990ac1a789d6dd0570cd9d03b1d2f9367cd93d1e7afd1ca865817819d3313
|
||||
|
|
@ -0,0 +1 @@
|
|||
4e328702ccd50d7062d82fec133e3acf66f4ca3e7bda2dd4a5f56c9e27baaf0f
|
||||
|
|
@ -557,6 +557,7 @@ Parameters:
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `search` | string | no | Return the list of authorized groups matching the search criteria |
|
||||
| `min_access_level` | integer | no | Limit to groups where current user has at least the specified [role (`access_level`)](members.md#roles) |
|
||||
| `relation` | array of strings | no | Filter the groups by relation (direct or inherited) |
|
||||
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
|
||||
|
||||
```plaintext
|
||||
|
|
|
|||
|
|
@ -2105,6 +2105,41 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## List a project's invited groups
|
||||
|
||||
Get a list of invited groups in the given project. When accessed without authentication, only public invited groups are returned.
|
||||
|
||||
By default, this request returns 20 results at a time because the API results [are paginated](rest/index.md#pagination).
|
||||
|
||||
Parameters:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------------------------- | ----------------- | -------- | ---------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `search` | string | no | Return the list of authorized groups matching the search criteria |
|
||||
| `min_access_level` | integer | no | Limit to groups where current user has at least the specified [role (`access_level`)](members.md#roles) |
|
||||
| `relation` | array of strings | no | Filter the groups by relation (direct or inherited) |
|
||||
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/invited_groups
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 35,
|
||||
"web_url": "https://gitlab.example.com/groups/twitter",
|
||||
"name": "Twitter",
|
||||
"avatar_url": null,
|
||||
"full_name": "Twitter",
|
||||
"full_path": "twitter"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Unstar a project
|
||||
|
||||
Unstars a given project. Returns status code `304` if the project is not starred.
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ module API
|
|||
tags %w[groups]
|
||||
end
|
||||
params do
|
||||
optional :relation, type: String, values: %w[direct inherited], desc: 'Include group relations'
|
||||
optional :relation, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: %w[direct inherited], desc: 'Include group relations'
|
||||
optional :search, type: String, desc: 'Search for a specific group'
|
||||
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Minimum access level of authenticated user'
|
||||
|
||||
|
|
@ -384,12 +384,10 @@ module API
|
|||
use :with_custom_attributes
|
||||
end
|
||||
get ":id/invited_groups", feature_category: :groups_and_projects do
|
||||
if Feature.enabled?(:rate_limit_groups_and_projects_api, current_user)
|
||||
check_rate_limit_by_user_or_ip!(:group_invited_groups_api)
|
||||
end
|
||||
check_rate_limit_by_user_or_ip!(:group_invited_groups_api)
|
||||
|
||||
group = find_group!(params[:id])
|
||||
groups = ::Namespaces::Groups::InvitedGroupsFinder.new(group, current_user, declared(params)).execute
|
||||
groups = ::Namespaces::Groups::InvitedGroupsFinder.new(group, current_user, declared_params).execute
|
||||
present_groups params, groups
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -894,6 +894,27 @@ module API
|
|||
present_groups groups
|
||||
end
|
||||
|
||||
desc 'Get a list of invited groups in this project' do
|
||||
success Entities::Group
|
||||
is_array true
|
||||
tags %w[projects]
|
||||
end
|
||||
params do
|
||||
optional :relation, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: %w[direct inherited], desc: 'Filter by group relation'
|
||||
optional :search, type: String, desc: 'Search for a specific group'
|
||||
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
|
||||
|
||||
use :pagination
|
||||
use :with_custom_attributes
|
||||
end
|
||||
get ':id/invited_groups', feature_category: :groups_and_projects do
|
||||
check_rate_limit_by_user_or_ip!(:project_invited_groups_api)
|
||||
|
||||
project = find_project!(params[:id])
|
||||
groups = ::Namespaces::Projects::InvitedGroupsFinder.new(project, current_user, declared_params).execute
|
||||
present_groups groups
|
||||
end
|
||||
|
||||
desc 'Start the housekeeping task for a project' do
|
||||
detail 'This feature was introduced in GitLab 9.0.'
|
||||
success code: 201
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ module Gitlab
|
|||
group_projects_api: { threshold: -> { application_settings.group_projects_api_limit }, interval: 1.minute },
|
||||
groups_api: { threshold: -> { application_settings.groups_api_limit }, interval: 1.minute },
|
||||
project_api: { threshold: -> { application_settings.project_api_limit }, interval: 1.minute },
|
||||
project_invited_groups_api: { threshold: -> { application_settings.project_invited_groups_api_limit }, interval: 1.minute },
|
||||
projects_api: { threshold: -> { application_settings.projects_api_limit }, interval: 10.minutes },
|
||||
user_contributed_projects_api: { threshold: -> { application_settings.user_contributed_projects_api_limit }, interval: 1.minute },
|
||||
user_projects_api: { threshold: -> { application_settings.user_projects_api_limit }, interval: 1.minute },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class CopyTaggingsToPCiBuildTags < BatchedMigrationJob
|
||||
operation_name :copy_taggings
|
||||
feature_category :continuous_integration
|
||||
|
||||
COLUMN_NAMES = [:tag_id, :build_id, :partition_id, :project_id].freeze
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
scope = sub_batch
|
||||
.where(taggable_type: 'CommitStatus')
|
||||
.joins('inner join p_ci_builds on p_ci_builds.id = taggings.taggable_id')
|
||||
.select(:tag_id, 'taggable_id as build_id', :partition_id, :project_id)
|
||||
|
||||
connection.execute(<<~SQL.squish)
|
||||
INSERT INTO p_ci_build_tags(tag_id, build_id, partition_id, project_id)
|
||||
(#{scope.to_sql})
|
||||
ON CONFLICT DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# rubocop: disable Migration/BatchedMigrationBaseClass -- rerun existing migration
|
||||
class RerunEpicDatesToWorkItemDatesSourcesSync < BackfillEpicDatesToWorkItemDatesSources
|
||||
operation_name :rerun_epic_dates_to_work_item_dates_sources_sync
|
||||
feature_category :team_planning
|
||||
end
|
||||
# rubocop: enable Migration/BatchedMigrationBaseClass
|
||||
end
|
||||
end
|
||||
|
|
@ -237,8 +237,25 @@ module Gitlab
|
|||
end
|
||||
|
||||
Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update)
|
||||
rescue GRPC::FailedPrecondition => e
|
||||
raise Gitlab::Git::CommitError, e
|
||||
rescue GRPC::BadStatus => e
|
||||
detailed_error = GitalyClient.decode_detailed_error(e)
|
||||
|
||||
case detailed_error.try(:error)
|
||||
when :custom_hook
|
||||
raise Gitlab::Git::PreReceiveError.new(custom_hook_error_message(detailed_error.custom_hook),
|
||||
fallback_message: CUSTOM_HOOK_FALLBACK_MESSAGE)
|
||||
when :reference_update
|
||||
# Historically UserFFBranch returned a successful response with a missing BranchUpdate if
|
||||
# updating the reference failed. The RPC has been updated to return a bad status when the
|
||||
# reference update fails. Match the previous behavior until call sites have been adapted.
|
||||
nil
|
||||
else
|
||||
if e.code == GRPC::Core::StatusCodes::FAILED_PRECONDITION
|
||||
raise Gitlab::Git::CommitError, e
|
||||
end
|
||||
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
|
|
|
|||
|
|
@ -19806,6 +19806,12 @@ msgstr ""
|
|||
msgid "DuoCodeReview|I have encountered some issues while I was reviewing. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseTrial|Activate my trial"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseTrial|Apply your GitLab Duo Enterprise trial to an existing group"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseTrial|Congratulations, your free GitLab Duo Enterprise trial is activated and will expire on %{exp_date}. The new license might take a minute to show on the page. To give members access to new GitLab Duo Enterprise features, %{assign_link_start}assign them%{assign_link_end} to GitLab Duo Enterprise seats."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -19836,6 +19842,9 @@ msgstr ""
|
|||
msgid "DuoEnterpriseTrial|Start your free GitLab Duo Enterprise trial on %{group_name}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseTrial|Start your free GitLab Duo Pro trial"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseTrial|Stay on top of regulatory requirements with self-hosted model deployment"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -965,6 +965,16 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do
|
|||
it_behaves_like 'API rate limit setting'
|
||||
end
|
||||
|
||||
context 'for GET /projects/:id/invited_groups API requests' do
|
||||
let_it_be(:rate_limit_field) do
|
||||
format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /projects/:id/invited_groups', timeframe: 'minute')
|
||||
end
|
||||
|
||||
let_it_be(:application_setting_key) { :project_invited_groups_api_limit }
|
||||
|
||||
it_behaves_like 'API rate limit setting'
|
||||
end
|
||||
|
||||
context 'for GET /users/:user_id/projects API requests' do
|
||||
let_it_be(:rate_limit_field) do
|
||||
format(_('Maximum requests to the %{api_name} API per %{timeframe} per user or IP address'), api_name: 'GET /users/:user_id/projects', timeframe: 'minute')
|
||||
|
|
|
|||
|
|
@ -63,16 +63,18 @@ RSpec.describe Namespaces::Groups::InvitedGroupsFinder, feature_category: :group
|
|||
let(:new_group) { create(:group) }
|
||||
let(:direct_group) { create(:group) }
|
||||
let(:sub_group) { create(:group, parent: new_group) }
|
||||
let(:direct_group_2) { create(:group) }
|
||||
|
||||
before do
|
||||
create(:group_group_link, shared_group: new_group, shared_with_group: direct_group)
|
||||
create(:group_group_link, shared_group: new_group, shared_with_group: sub_group)
|
||||
create(:group_group_link, shared_group: sub_group, shared_with_group: direct_group_2)
|
||||
end
|
||||
|
||||
subject(:results) { described_class.new(new_group, current_user, params).execute }
|
||||
|
||||
context 'when relation is direct' do
|
||||
let(:params) { { relation: "direct" } }
|
||||
let(:params) { { relation: ["direct"] } }
|
||||
|
||||
it 'returns only direct invited groups' do
|
||||
expect(results).to contain_exactly(direct_group, sub_group)
|
||||
|
|
@ -80,7 +82,7 @@ RSpec.describe Namespaces::Groups::InvitedGroupsFinder, feature_category: :group
|
|||
end
|
||||
|
||||
context 'when no inherited relation is present' do
|
||||
let(:params) { { relation: "inherited" } }
|
||||
let(:params) { { relation: ["inherited"] } }
|
||||
|
||||
it 'returns no invited groups' do
|
||||
expect(results).to be_empty
|
||||
|
|
@ -88,14 +90,24 @@ RSpec.describe Namespaces::Groups::InvitedGroupsFinder, feature_category: :group
|
|||
end
|
||||
|
||||
context 'when inherited relation is present with respect to sub group' do
|
||||
let(:params) { { relation: "inherited" } }
|
||||
let(:params) { { relation: %w[inherited] } }
|
||||
|
||||
subject(:results) { described_class.new(sub_group, current_user, params).execute }
|
||||
|
||||
it 'returns no invited groups' do
|
||||
it 'returns invited groups' do
|
||||
expect(results).to contain_exactly(sub_group, direct_group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when direct and inherited relation is present with respect to sub group' do
|
||||
let(:params) { { relation: %w[inherited direct] } }
|
||||
|
||||
subject(:results) { described_class.new(sub_group, current_user, params).execute }
|
||||
|
||||
it 'returns all invited groups' do
|
||||
expect(results).to contain_exactly(sub_group, direct_group, direct_group_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Namespaces::Projects::InvitedGroupsFinder, feature_category: :groups_and_projects do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:current_user) { user }
|
||||
let_it_be(:another_user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, owners: user) }
|
||||
let_it_be(:other_group) { create(:group, owners: user, name: "other group") }
|
||||
let_it_be(:private_group) { create(:group, :private) }
|
||||
let_it_be(:project) { create(:project, owners: user) }
|
||||
let(:group_access) { Gitlab::Access::DEVELOPER }
|
||||
let(:params) { {} }
|
||||
|
||||
subject(:results) { described_class.new(project, current_user, params).execute }
|
||||
|
||||
before do
|
||||
create(:project_group_link, group: group, project: project)
|
||||
create(:project_group_link, group: other_group, project: project)
|
||||
create(:project_group_link, group: private_group, project: project)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
context 'when the user has permission to read the group' do
|
||||
let(:current_user) { user }
|
||||
|
||||
it 'returns the shared groups which is public or visible to the user' do
|
||||
expect(results).to contain_exactly(group, other_group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user does not have permission to read the group' do
|
||||
let(:current_user) { another_user }
|
||||
|
||||
it 'returns no groups' do
|
||||
expect(results).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with search filter' do
|
||||
let(:params) { { search: "other group" } }
|
||||
|
||||
it 'filters by search term' do
|
||||
expect(results).to contain_exactly(other_group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with min_access_level filter' do
|
||||
before_all do
|
||||
group.add_owner(current_user)
|
||||
other_group.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
let(:params) { { min_access_level: Gitlab::Access::OWNER } }
|
||||
|
||||
it 'filters by minimum access level' do
|
||||
expect(results).to contain_exactly(group)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with include relations filter' do
|
||||
let_it_be(:direct_group1) { create(:group, owners: current_user) }
|
||||
let_it_be(:direct_group2) { create(:group, owners: current_user) }
|
||||
let_it_be(:inherited_group1) { create(:group, owners: current_user) }
|
||||
let_it_be(:inherited_group2) { create(:group, owners: current_user) }
|
||||
let_it_be(:project1) { create(:project, group: direct_group1, owners: current_user) }
|
||||
|
||||
before do
|
||||
create(:project_group_link, group: direct_group2, project: project1)
|
||||
create(:group_group_link, shared_group: direct_group1, shared_with_group: inherited_group2)
|
||||
create(:group_group_link, shared_group: direct_group1, shared_with_group: inherited_group1)
|
||||
end
|
||||
|
||||
subject(:results) { described_class.new(project1, current_user, params).execute }
|
||||
|
||||
context 'when relation is direct' do
|
||||
let(:params) { { relation: ["direct"] } }
|
||||
|
||||
it 'returns only direct invited groups' do
|
||||
expect(results).to contain_exactly(direct_group2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when relation is inherited' do
|
||||
let(:params) { { relation: ["inherited"] } }
|
||||
|
||||
it 'returns inherited invited groups' do
|
||||
expect(results).to contain_exactly(inherited_group1, inherited_group2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no relation params is present' do
|
||||
it 'returns all invited groups' do
|
||||
expect(results).to contain_exactly(direct_group2, inherited_group1, inherited_group2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when direct and inherited relation params is present' do
|
||||
let(:params) { { relation: %w[direct inherited] } }
|
||||
|
||||
it 'returns all invited groups' do
|
||||
expect(results).to contain_exactly(direct_group2, inherited_group1, inherited_group2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -76,3 +76,8 @@ artifacts-access-all:
|
|||
artifacts-access-invalid-value:
|
||||
artifacts:
|
||||
access: random
|
||||
|
||||
annotations-report-annotations-not-string:
|
||||
artifacts:
|
||||
reports:
|
||||
annotations: 1
|
||||
|
|
|
|||
|
|
@ -63,3 +63,8 @@ artifacts-access-all:
|
|||
artifacts-access-none:
|
||||
artifacts:
|
||||
access: none
|
||||
|
||||
annotations-report-annotations-normal:
|
||||
artifacts:
|
||||
reports:
|
||||
annotations: upload_report.json
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ RSpec.describe ApplicationSettingsHelper do
|
|||
user_contributed_projects_api_limit user_projects_api_limit user_starred_projects_api_limit
|
||||
group_shared_groups_api_limit
|
||||
group_invited_groups_api_limit
|
||||
project_invited_groups_api_limit
|
||||
])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::CopyTaggingsToPCiBuildTags, feature_category: :continuous_integration do
|
||||
let(:ci_pipelines_table) { table(:ci_pipelines, database: :ci, primary_key: :id) }
|
||||
let(:ci_builds_table) { table(:p_ci_builds, database: :ci, primary_key: :id) }
|
||||
let(:ci_build_tags_table) { table(:p_ci_build_tags, database: :ci, primary_key: :id) }
|
||||
let(:taggings_table) { table(:taggings, database: :ci) }
|
||||
let(:tags_table) { table(:tags, database: :ci) }
|
||||
|
||||
let(:pipeline1) { ci_pipelines_table.create!(partition_id: 100, project_id: 1) }
|
||||
let(:pipeline2) { ci_pipelines_table.create!(partition_id: 101, project_id: 2) }
|
||||
|
||||
let(:job1) { ci_builds_table.create!(partition_id: 100, project_id: 1, commit_id: pipeline1.id) }
|
||||
let(:job2) { ci_builds_table.create!(partition_id: 100, project_id: 2, commit_id: pipeline1.id) }
|
||||
|
||||
let(:tag1) { tags_table.create!(name: 'docker') }
|
||||
let(:tag2) { tags_table.create!(name: 'postgres') }
|
||||
let(:tag3) { tags_table.create!(name: 'ruby') }
|
||||
let(:tag4) { tags_table.create!(name: 'golang') }
|
||||
|
||||
let(:migration_attrs) do
|
||||
{
|
||||
start_id: taggings_table.minimum(:id),
|
||||
end_id: taggings_table.maximum(:id),
|
||||
batch_table: :taggings,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 1,
|
||||
pause_ms: 0,
|
||||
connection: connection
|
||||
}
|
||||
end
|
||||
|
||||
let(:migration) { described_class.new(**migration_attrs) }
|
||||
let(:connection) { Ci::ApplicationRecord.connection }
|
||||
|
||||
before do
|
||||
taggings_table.create!(tag_id: tag1.id, taggable_id: job1.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag2.id, taggable_id: job1.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag3.id, taggable_id: job1.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag1.id, taggable_id: job2.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag2.id, taggable_id: job2.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag4.id, taggable_id: job2.id, taggable_type: 'CommitStatus', context: :tags)
|
||||
taggings_table.create!(tag_id: tag3.id, taggable_id: 5, taggable_type: 'Ci::Runner', context: :tags)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it 'copies records over into p_ci_build_tags' do
|
||||
expect { migration.perform }
|
||||
.to change { ci_build_tags_table.count }
|
||||
.from(0)
|
||||
.to(6)
|
||||
|
||||
expect(taggings_table.where(taggable_id: job1).pluck(:tag_id))
|
||||
.to match_array(ci_build_tags_table.where(build_id: job1).pluck(:tag_id))
|
||||
|
||||
expect(taggings_table.where(taggable_id: job2).pluck(:tag_id))
|
||||
.to match_array(ci_build_tags_table.where(build_id: job2).pluck(:tag_id))
|
||||
|
||||
expect(ci_build_tags_table.where(build_id: job1).pluck(:project_id).uniq)
|
||||
.to contain_exactly(1)
|
||||
|
||||
expect(ci_build_tags_table.where(build_id: job2).pluck(:project_id).uniq)
|
||||
.to contain_exactly(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::RerunEpicDatesToWorkItemDatesSourcesSync,
|
||||
feature_category: :team_planning do
|
||||
let!(:epic_type_id) { table(:work_item_types).find_by(base_type: 7).id }
|
||||
let!(:author) { table(:users).create!(username: 'tester', projects_limit: 100) }
|
||||
let!(:namespace) { table(:namespaces).create!(name: 'my test group1', path: 'my-test-group1') }
|
||||
|
||||
let(:milestone) do
|
||||
table(:milestones).create!(
|
||||
title: 'Milestone',
|
||||
start_date: DateTime.parse('2024-01-01'),
|
||||
due_date: DateTime.parse('2024-01-31')
|
||||
)
|
||||
end
|
||||
|
||||
let(:epics) { table(:epics) }
|
||||
let(:issues) { table(:issues) }
|
||||
let(:work_item_dates_sources) { table(:work_item_dates_sources) }
|
||||
let(:start_id) { epics.minimum(:id) }
|
||||
let(:end_id) { epics.maximum(:id) }
|
||||
|
||||
let!(:fixed_epic_1) do
|
||||
create_epic_with_work_item(title: 'Epic 5', iid: 5, date_attrs: with_fixed_dates('2024-02-01', '2024-02-29'))
|
||||
end
|
||||
|
||||
let!(:fixed_epic_2) do
|
||||
create_epic_with_work_item(title: 'Epic 6', iid: 6, date_attrs: with_fixed_dates('2024-03-01', '2024-03-31'))
|
||||
end
|
||||
|
||||
let!(:fixed_epic_3) do
|
||||
create_epic_with_work_item(title: 'Epic 7', iid: 7, date_attrs: with_fixed_dates('2024-04-01', '2024-04-30'))
|
||||
end
|
||||
|
||||
let!(:fixed_epic_4) do
|
||||
create_epic_with_work_item(title: 'Epic 8', iid: 8, date_attrs: with_fixed_dates('2024-05-01', '2024-05-31'))
|
||||
end
|
||||
|
||||
let!(:fixed_epic_5) do
|
||||
create_epic_with_work_item(title: 'Epic 9', iid: 9, date_attrs: with_fixed_dates('2024-06-01', '2024-06-30'))
|
||||
end
|
||||
|
||||
let!(:rolledup_epic_1) do
|
||||
create_epic_with_work_item(
|
||||
title: 'Epic 10',
|
||||
iid: 10,
|
||||
date_attrs: {
|
||||
start_date_is_fixed: false,
|
||||
due_date_is_fixed: false,
|
||||
start_date: fixed_epic_1.start_date,
|
||||
end_date: milestone.due_date,
|
||||
start_date_sourcing_milestone_id: nil,
|
||||
due_date_sourcing_milestone_id: milestone.id,
|
||||
start_date_sourcing_epic_id: fixed_epic_1.id,
|
||||
due_date_sourcing_epic_id: nil
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let!(:rolledup_epic_2) do
|
||||
create_epic_with_work_item(
|
||||
title: 'Epic 11',
|
||||
iid: 11,
|
||||
date_attrs: {
|
||||
start_date_is_fixed: false,
|
||||
due_date_is_fixed: false,
|
||||
start_date: fixed_epic_2.start_date,
|
||||
end_date: fixed_epic_3.end_date,
|
||||
start_date_sourcing_milestone_id: nil,
|
||||
due_date_sourcing_milestone_id: nil,
|
||||
start_date_sourcing_epic_id: fixed_epic_2.id,
|
||||
due_date_sourcing_epic_id: fixed_epic_3.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let!(:rolledup_epic_3) do
|
||||
create_epic_with_work_item(
|
||||
title: 'Epic 12',
|
||||
iid: 12,
|
||||
date_attrs: {
|
||||
start_date_is_fixed: false,
|
||||
due_date_is_fixed: nil,
|
||||
start_date_fixed: DateTime.parse('2024-07-01'),
|
||||
due_date_fixed: DateTime.parse('2024-07-31'),
|
||||
start_date: fixed_epic_4.start_date,
|
||||
end_date: fixed_epic_4.end_date,
|
||||
start_date_sourcing_milestone_id: nil,
|
||||
due_date_sourcing_milestone_id: nil,
|
||||
start_date_sourcing_epic_id: fixed_epic_4.id,
|
||||
due_date_sourcing_epic_id: fixed_epic_4.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let!(:rolledup_epic_4) do
|
||||
create_epic_with_work_item(
|
||||
title: 'Epic 13',
|
||||
iid: 13,
|
||||
date_attrs: {
|
||||
start_date_is_fixed: false,
|
||||
due_date_is_fixed: false,
|
||||
start_date_fixed: DateTime.parse('2024-08-01'),
|
||||
due_date_fixed: DateTime.parse('2024-08-31'),
|
||||
start_date: fixed_epic_5.start_date,
|
||||
end_date: fixed_epic_5.end_date,
|
||||
start_date_sourcing_milestone_id: nil,
|
||||
due_date_sourcing_milestone_id: nil,
|
||||
start_date_sourcing_epic_id: fixed_epic_5.id,
|
||||
due_date_sourcing_epic_id: fixed_epic_5.id
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let!(:rolledup_epic_5) do
|
||||
create_epic_with_work_item(
|
||||
title: 'Epic 14',
|
||||
iid: 14,
|
||||
date_attrs: {
|
||||
start_date_is_fixed: nil,
|
||||
due_date_is_fixed: true,
|
||||
start_date_fixed: DateTime.parse('2024-09-01'),
|
||||
due_date_fixed: DateTime.parse('2024-09-30'),
|
||||
start_date: fixed_epic_5.start_date,
|
||||
end_date: DateTime.parse('2024-09-30'),
|
||||
start_date_sourcing_milestone_id: nil,
|
||||
due_date_sourcing_milestone_id: nil,
|
||||
start_date_sourcing_epic_id: fixed_epic_5.id,
|
||||
due_date_sourcing_epic_id: nil
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
# Existing date_source for fixed_epic_1 that is not in sync
|
||||
let!(:not_synced_date_source) do
|
||||
work_item_dates_sources.create!(namespace_id: namespace.id, issue_id: fixed_epic_1.issue_id)
|
||||
end
|
||||
|
||||
# Existing date_source for rolledup_epic_4 that is fully synced
|
||||
let!(:synced_date_source) do
|
||||
work_item_dates_sources.create!(
|
||||
namespace_id: namespace.id,
|
||||
issue_id: rolledup_epic_4.issue_id,
|
||||
start_date_is_fixed: rolledup_epic_4.start_date_is_fixed,
|
||||
due_date_is_fixed: rolledup_epic_4.due_date_is_fixed,
|
||||
start_date_fixed: rolledup_epic_4.start_date_fixed,
|
||||
due_date_fixed: rolledup_epic_4.due_date_fixed,
|
||||
start_date: rolledup_epic_4.start_date,
|
||||
due_date: rolledup_epic_4.end_date,
|
||||
start_date_sourcing_work_item_id: fixed_epic_5.issue_id,
|
||||
due_date_sourcing_work_item_id: fixed_epic_5.issue_id
|
||||
)
|
||||
end
|
||||
|
||||
context 'when backfilling all epics', :aggregate_failures do
|
||||
subject(:migration) do
|
||||
described_class.new(
|
||||
start_id: start_id,
|
||||
end_id: end_id,
|
||||
batch_table: :epics,
|
||||
batch_column: :id,
|
||||
job_arguments: [nil],
|
||||
sub_batch_size: 2,
|
||||
pause_ms: 2,
|
||||
connection: ::ApplicationRecord.connection
|
||||
)
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :match_synced_work_item_dates do
|
||||
match do |epic|
|
||||
date_source = work_item_dates_sources.find_by_issue_id(epic.issue_id)
|
||||
|
||||
expect(date_source.start_date).to eq epic.start_date
|
||||
expect(date_source.start_date_is_fixed).to eq epic.start_date_is_fixed.present?
|
||||
expect(date_source.due_date_is_fixed).to eq epic.due_date_is_fixed.present?
|
||||
expect(date_source.start_date_fixed).to eq epic.start_date_fixed
|
||||
expect(date_source.due_date_fixed).to eq epic.due_date_fixed
|
||||
expect(date_source.namespace_id).to eq(epic.group_id)
|
||||
expect(date_source.due_date).to eq(epic.end_date)
|
||||
expect(date_source.start_date_sourcing_milestone_id).to eq(epic.start_date_sourcing_milestone_id)
|
||||
expect(date_source.due_date_sourcing_milestone_id).to eq(epic.due_date_sourcing_milestone_id)
|
||||
expect(date_source.start_date_sourcing_work_item_id)
|
||||
.to eq(epics.find_by_id(epic.start_date_sourcing_epic_id)&.issue_id)
|
||||
expect(date_source.due_date_sourcing_work_item_id)
|
||||
.to eq(epics.find_by_id(epic.due_date_sourcing_epic_id)&.issue_id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'backfills data correctly' do
|
||||
expect { migration.perform }
|
||||
.to change { work_item_dates_sources.count }.from(2).to(10)
|
||||
.and not_change { synced_date_source }
|
||||
|
||||
expect(epics.all).to all(match_synced_work_item_dates)
|
||||
end
|
||||
end
|
||||
|
||||
def create_epic_with_work_item(iid:, title:, date_attrs: {})
|
||||
wi = issues.create!(
|
||||
iid: iid,
|
||||
author_id: author.id,
|
||||
work_item_type_id: epic_type_id,
|
||||
namespace_id: namespace.id,
|
||||
lock_version: 1,
|
||||
title: title
|
||||
)
|
||||
|
||||
epic_attributes = {
|
||||
iid: iid,
|
||||
title: title,
|
||||
title_html: title,
|
||||
group_id: namespace.id,
|
||||
author_id: author.id,
|
||||
issue_id: wi.id
|
||||
}
|
||||
|
||||
epics.create!(epic_attributes.merge!(date_attrs))
|
||||
end
|
||||
|
||||
def with_fixed_dates(start_date, due_date)
|
||||
{
|
||||
start_date: DateTime.parse(start_date),
|
||||
end_date: DateTime.parse(due_date),
|
||||
start_date_fixed: DateTime.parse(start_date),
|
||||
due_date_fixed: DateTime.parse(due_date),
|
||||
start_date_is_fixed: true,
|
||||
due_date_is_fixed: true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -518,12 +518,6 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source
|
|||
|
||||
let(:response) { Gitaly::UserFFBranchResponse.new(branch_update: branch_update) }
|
||||
|
||||
before do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_ff_branch).with(request, kind_of(Hash))
|
||||
.and_return(response)
|
||||
end
|
||||
|
||||
subject do
|
||||
client.user_ff_branch(user,
|
||||
source_sha: source_sha,
|
||||
|
|
@ -532,30 +526,109 @@ RSpec.describe Gitlab::GitalyClient::OperationService, feature_category: :source
|
|||
)
|
||||
end
|
||||
|
||||
it 'sends a user_ff_branch message and returns a BranchUpdate object' do
|
||||
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
|
||||
expect(subject.newrev).to eq(source_sha)
|
||||
expect(subject.repo_created).to be(false)
|
||||
expect(subject.branch_created).to be(false)
|
||||
end
|
||||
|
||||
context 'when the response has no branch_update' do
|
||||
let(:response) { Gitaly::UserFFBranchResponse.new }
|
||||
|
||||
it { expect(subject).to be_nil }
|
||||
end
|
||||
|
||||
context "when the pre-receive hook fails" do
|
||||
let(:response) do
|
||||
Gitaly::UserFFBranchResponse.new(
|
||||
branch_update: nil,
|
||||
pre_receive_error: "pre-receive hook error message\n"
|
||||
)
|
||||
context 'with response' do
|
||||
before do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_ff_branch).with(request, kind_of(Hash))
|
||||
.and_return(response)
|
||||
end
|
||||
|
||||
it "raises the error" do
|
||||
# the PreReceiveError class strips the GL-HOOK-ERR prefix from this error
|
||||
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "pre-receive hook failed.")
|
||||
it 'sends a user_ff_branch message and returns a BranchUpdate object' do
|
||||
expect(subject).to be_a(Gitlab::Git::OperationService::BranchUpdate)
|
||||
expect(subject.newrev).to eq(source_sha)
|
||||
expect(subject.repo_created).to be(false)
|
||||
expect(subject.branch_created).to be(false)
|
||||
end
|
||||
|
||||
context 'when the response has no branch_update' do
|
||||
let(:response) { Gitaly::UserFFBranchResponse.new }
|
||||
|
||||
it { expect(subject).to be_nil }
|
||||
end
|
||||
|
||||
context "when the pre-receive hook fails" do
|
||||
let(:response) do
|
||||
Gitaly::UserFFBranchResponse.new(
|
||||
branch_update: nil,
|
||||
pre_receive_error: "pre-receive hook error message\n"
|
||||
)
|
||||
end
|
||||
|
||||
it "raises the error" do
|
||||
# the PreReceiveError class strips the GL-HOOK-ERR prefix from this error
|
||||
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "pre-receive hook failed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with exception' do
|
||||
before do
|
||||
expect_any_instance_of(Gitaly::OperationService::Stub)
|
||||
.to receive(:user_ff_branch).with(request, kind_of(Hash))
|
||||
.and_raise(exception)
|
||||
end
|
||||
|
||||
context 'with CustomHookError' do
|
||||
let(:exception) do
|
||||
new_detailed_error(
|
||||
GRPC::Core::StatusCodes::PERMISSION_DENIED,
|
||||
"custom hook error",
|
||||
Gitaly::UserFFBranchError.new(
|
||||
custom_hook: Gitaly::CustomHookError.new(
|
||||
stdout: "some stdout",
|
||||
stderr: "GitLab: some custom hook error message",
|
||||
hook_type: Gitaly::CustomHookError::HookType::HOOK_TYPE_PRERECEIVE
|
||||
)))
|
||||
end
|
||||
|
||||
it 'raises a PreReceiveError' do
|
||||
expect { subject }.to raise_error do |error|
|
||||
expect(error).to be_a(Gitlab::Git::PreReceiveError)
|
||||
expect(error.message).to eq("some custom hook error message")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with ReferenceUpdateError' do
|
||||
let(:exception) do
|
||||
new_detailed_error(GRPC::Core::StatusCodes::FAILED_PRECONDITION,
|
||||
"some ignored error message",
|
||||
Gitaly::UserFFBranchError.new(reference_update: Gitaly::ReferenceUpdateError.new))
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with FailedPrecondition' do
|
||||
let(:exception) do
|
||||
GRPC::FailedPrecondition.new('failed precondition error')
|
||||
end
|
||||
|
||||
it 'returns CommitError' do
|
||||
expect { subject }.to raise_error(Gitlab::Git::CommitError, exception.message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a bad status' do
|
||||
let(:exception) do
|
||||
GRPC::Internal.new('internal error')
|
||||
end
|
||||
|
||||
it 'raises the exception' do
|
||||
expect { subject }.to raise_error(GRPC::Internal, exception.message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unhandled exception' do
|
||||
let(:exception) do
|
||||
RuntimeError.new('unhandled exception')
|
||||
end
|
||||
|
||||
it 'raises the exception' do
|
||||
expect { subject }.to raise_error(RuntimeError, exception.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueCopyTaggingsToPCiBuildTags, migration: :gitlab_ci, feature_category: :continuous_integration 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: :taggings,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_ci
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
context 'when executed on .com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com_except_jh?).and_return(true)
|
||||
end
|
||||
|
||||
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(
|
||||
gitlab_schema: :gitlab_ci,
|
||||
table_name: :taggings,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::GITLAB_OPTIMIZED_BATCH_SIZE,
|
||||
sub_batch_size: described_class::GITLAB_OPTIMIZED_SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueRerunEpicDatesToWorkItemDatesSourcesSync, feature_category: :team_planning 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: :epics,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -42,6 +42,7 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do
|
|||
it { expect(setting.group_shared_groups_api_limit).to eq(60) }
|
||||
it { expect(setting.groups_api_limit).to eq(200) }
|
||||
it { expect(setting.project_api_limit).to eq(400) }
|
||||
it { expect(setting.project_invited_groups_api_limit).to eq(60) }
|
||||
it { expect(setting.projects_api_limit).to eq(2000) }
|
||||
it { expect(setting.receptive_cluster_agents_enabled).to eq(false) }
|
||||
it { expect(setting.user_contributed_projects_api_limit).to eq(100) }
|
||||
|
|
|
|||
|
|
@ -1480,6 +1480,47 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe ".username_reserved?" do
|
||||
subject(:username_reserved) { described_class.username_reserved?(username) }
|
||||
|
||||
let(:username) { 'capyabra' }
|
||||
|
||||
let_it_be(:user) { create(:user, name: 'capybara') }
|
||||
let_it_be(:group) { create(:group, name: 'capybara-group') }
|
||||
let_it_be(:subgroup) { create(:group, parent: group, name: 'capybara-subgroup') }
|
||||
let_it_be(:project) { create(:project, group: group, name: 'capybara-project') }
|
||||
|
||||
context 'when given a project name' do
|
||||
let(:username) { 'capyabra-project' }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when given a sub-group name' do
|
||||
let(:username) { 'capybara-subgroup' }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
|
||||
context 'when given a top-level group' do
|
||||
let(:username) { 'capybara-group' }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when given an existing username' do
|
||||
let(:username) { 'capybara' }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when given a username with varying capitalization' do
|
||||
let(:username) { 'CaPyBaRa' }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#default_branch_protection" do
|
||||
let(:namespace) { create(:namespace) }
|
||||
let(:default_branch_protection) { nil }
|
||||
|
|
|
|||
|
|
@ -2231,18 +2231,6 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when rate_limit_groups_and_projects_api feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(rate_limit_groups_and_projects_api: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'unthrottled endpoint'
|
||||
|
||||
def request
|
||||
get api(path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as user' do
|
||||
it 'returns the invited groups in the group', :aggregate_failures do
|
||||
expect_log_keys(caller_id: "GET /api/:version/groups/:id/invited_groups",
|
||||
|
|
@ -2354,7 +2342,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
it 'filters the invited groups in the group based on relation params', :aggregate_failures do
|
||||
get api("/groups/#{relation_main_group.id}/invited_groups", user1), params: { relation: 'direct' }
|
||||
get api("/groups/#{relation_main_group.id}/invited_groups", user1), params: { relation: ['direct'] }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
|
|
@ -2363,7 +2351,7 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
it 'returns error message when include relation is invalid' do
|
||||
get api("/groups/#{relation_main_group.id}/invited_groups", user1), params: { relation: 'some random' }
|
||||
get api("/groups/#{relation_main_group.id}/invited_groups", user1), params: { relation: ['some random'] }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq("relation does not have a valid value")
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
|
|||
include StubRequests
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user1) { create(:user) }
|
||||
let_it_be(:user2) { create(:user) }
|
||||
let_it_be(:user3) { create(:user) }
|
||||
let_it_be(:admin) { create(:admin) }
|
||||
|
|
@ -3749,6 +3750,160 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET /projects/:id/invited_groups' do
|
||||
let_it_be(:main_group) { create(:group, :private, owners: user1) }
|
||||
let_it_be(:direct_group1) { create(:group, :private, owners: user1) }
|
||||
let_it_be(:direct_group2) { create(:group, :private, owners: user1) }
|
||||
let_it_be(:inherited_group) { create(:group, :private, owners: user1) }
|
||||
let_it_be(:main_project) { create(:project, group: main_group, owners: user1) }
|
||||
|
||||
let(:path) { "/projects/#{main_project.id}/invited_groups" }
|
||||
|
||||
before do
|
||||
create(:group_group_link, shared_group: main_group, shared_with_group: inherited_group)
|
||||
create(:project_group_link, group: direct_group1, project: main_project)
|
||||
create(:project_group_link, group: direct_group2, project: main_project)
|
||||
end
|
||||
|
||||
it_behaves_like 'rate limited endpoint', rate_limit_key: :project_invited_groups_api do
|
||||
def request
|
||||
get api(path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as user' do
|
||||
it 'returns the invited groups in the project', :aggregate_failures do
|
||||
get api(path, user1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response.length).to eq(3)
|
||||
group_ids = json_response.map { |group| group['id'] }
|
||||
expect(group_ids).to contain_exactly(direct_group1.id, direct_group2.id, inherited_group.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated and user does not have the access' do
|
||||
it 'does not return the invited groups in the project', :aggregate_failures do
|
||||
get api(path, user2)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when unauthenticated as user' do
|
||||
let_it_be(:main_group) { create(:group, :public, owners: user2) }
|
||||
let_it_be(:direct_group_1) { create(:group, :public, owners: user2) }
|
||||
let_it_be(:direct_group_2) { create(:group, :private, owners: user2) }
|
||||
let_it_be(:new_project) { create(:project, :public, group: main_group, owners: user2) }
|
||||
|
||||
let(:path) { "/projects/#{new_project.id}/invited_groups" }
|
||||
|
||||
before do
|
||||
create(:project_group_link, group: direct_group_1, project: new_project)
|
||||
create(:project_group_link, group: direct_group_2, project: new_project)
|
||||
end
|
||||
|
||||
it 'only returns the invited public groups in the project', :aggregate_failures do
|
||||
get api(path)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response.length).to eq(1)
|
||||
group_ids = json_response.map { |group| group['id'] }
|
||||
expect(group_ids).to contain_exactly(direct_group_1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "when search is present in request" do
|
||||
let_it_be(:direct_group_1) { create(:group, :public, name: "new direct", owners: user1) }
|
||||
let_it_be(:direct_group_2) { create(:group, :private, name: "other direct", owners: user1) }
|
||||
let_it_be(:new_project) { create(:project, :public, owners: user1) }
|
||||
|
||||
let(:path) { "/projects/#{new_project.id}/invited_groups" }
|
||||
|
||||
before do
|
||||
create(:project_group_link, group: direct_group_1, project: new_project)
|
||||
create(:project_group_link, group: direct_group_2, project: new_project)
|
||||
end
|
||||
|
||||
it 'filters the invited groups in the group based on search params', :aggregate_failures do
|
||||
get api(path, user1), params: { search: 'new' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['id']).to eq(direct_group_1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using min_access_level in the request' do
|
||||
let_it_be(:new_direct_group) { create(:group, :public, name: "new direct") }
|
||||
let_it_be(:other_direct_group) { create(:group, :private, name: "other direct") }
|
||||
let_it_be(:new_project) { create(:project, :public) }
|
||||
|
||||
let(:path) { "/projects/#{new_project.id}/invited_groups" }
|
||||
|
||||
before do
|
||||
new_direct_group.add_developer(user1)
|
||||
other_direct_group.add_owner(user1)
|
||||
create(:project_group_link, group: new_direct_group, project: new_project)
|
||||
create(:project_group_link, group: other_direct_group, project: new_project)
|
||||
end
|
||||
|
||||
context 'with min_access_level parameter' do
|
||||
it 'returns an array of groups the user has at least owner access', :aggregate_failures do
|
||||
get api(path, user1), params: { min_access_level: Gitlab::Access::OWNER }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.map { |group| group['id'] }).to contain_exactly(other_direct_group.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when include_relation is present in request" do
|
||||
let_it_be(:relation_main_group) { create(:group, :private, owners: user1) }
|
||||
let_it_be(:direct_group) { create(:group, owners: user1) }
|
||||
let_it_be(:inherited_group) { create(:group, owners: user1) }
|
||||
let_it_be(:new_relation_project) { create(:project, group: relation_main_group) }
|
||||
|
||||
let(:path) { "/projects/#{new_relation_project.id}/invited_groups" }
|
||||
|
||||
before do
|
||||
create(:project_group_link, group: direct_group, project: new_relation_project)
|
||||
create(:group_group_link, shared_group: relation_main_group, shared_with_group: inherited_group)
|
||||
end
|
||||
|
||||
it 'filters the invited groups in the project based on direct relation params', :aggregate_failures do
|
||||
get api(path, user1), params: { relation: ['direct'] }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.map { |group| group['id'] }).to contain_exactly(direct_group.id)
|
||||
end
|
||||
|
||||
it 'filters the invited groups in the project based on inherited relation params', :aggregate_failures do
|
||||
get api(path, user1), params: { relation: ['inherited'] }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to include_pagination_headers
|
||||
expect(json_response).to be_an(Array)
|
||||
expect(json_response.map { |group| group['id'] }).to contain_exactly(inherited_group.id)
|
||||
end
|
||||
|
||||
it 'returns error message when include relation is invalid' do
|
||||
get api(path, user1), params: { relation: ['some random'] }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq("relation does not have a valid value")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /projects/:id/share/:group_id' do
|
||||
context 'for a valid group' do
|
||||
let_it_be(:group) { create(:group, :private) }
|
||||
|
|
|
|||
|
|
@ -754,11 +754,12 @@ RSpec.describe UsersController, feature_category: :user_management do
|
|||
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(false)
|
||||
end
|
||||
|
||||
let(:exists_true_response_body) { { exists: true }.to_json }
|
||||
|
||||
it 'returns JSON indicating the user exists' do
|
||||
get user_exists_url user.username
|
||||
|
||||
expected_json = { exists: true }.to_json
|
||||
expect(response.body).to eq(expected_json)
|
||||
expect(response.body).to eq(exists_true_response_body)
|
||||
end
|
||||
|
||||
context 'when the casing is different' do
|
||||
|
|
@ -767,8 +768,24 @@ RSpec.describe UsersController, feature_category: :user_management do
|
|||
it 'returns JSON indicating the user exists' do
|
||||
get user_exists_url user.username.downcase
|
||||
|
||||
expected_json = { exists: true }.to_json
|
||||
expect(response.body).to eq(expected_json)
|
||||
expect(response.body).to eq(exists_true_response_body)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a group with the username exists' do
|
||||
let_it_be(:group) { create(:group, name: 'get-user-exists') }
|
||||
let_it_be(:subgroup) { create(:group, name: 'get-user-exists-child', parent: group) }
|
||||
|
||||
it 'treats the top-level group as a reserved name' do
|
||||
get user_exists_url 'get-user-exists'
|
||||
|
||||
expect(response.body).to eq(exists_true_response_body)
|
||||
end
|
||||
|
||||
it 'treats the sub-group as not a reserved name' do
|
||||
get user_exists_url 'get-user-exists-child'
|
||||
|
||||
expect(response.body).to eq({ exists: false }.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue