diff --git a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml index d708ab21908..591595f66bf 100644 --- a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml +++ b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml @@ -97,7 +97,7 @@ variables: - echo -e "\e[0Ksection_end:`date +%s`:launch_gdk\r\e[0K" - echo -e "\e[0Ksection_start:`date +%s`:install_gems[collapsed=true]\r\e[0KInstall gems" - source scripts/utils.sh - - cd qa && bundle install + - cd qa && bundle config set --local without 'development' && bundle install - echo -e "\e[0Ksection_end:`date +%s`:install_gems\r\e[0K" script: - echo -e "\e[0Ksection_start:`date +%s`:healthcheck[collapsed=true]\r\e[0KWait for gdk to start" diff --git a/.rubocop_todo/layout/space_in_lambda_literal.yml b/.rubocop_todo/layout/space_in_lambda_literal.yml index 56be3b2a759..7e3af689b5f 100644 --- a/.rubocop_todo/layout/space_in_lambda_literal.yml +++ b/.rubocop_todo/layout/space_in_lambda_literal.yml @@ -271,7 +271,6 @@ Layout/SpaceInLambdaLiteral: - 'ee/app/services/vulnerability_exports/exporters/csv_service.rb' - 'ee/app/workers/update_all_mirrors_worker.rb' - 'ee/lib/api/entities/pending_member.rb' - - 'ee/lib/api/ml/ai_assist.rb' - 'ee/lib/ee/api/entities/ci/job_request/response.rb' - 'ee/lib/ee/api/entities/epic.rb' - 'ee/lib/ee/api/entities/issue.rb' diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 4d3647cdf5c..74d91734630 100644 --- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -5,9 +5,10 @@ import { TOKEN_TYPE_APPROVED_BY, TOKEN_TYPE_REVIEWER, TOKEN_TYPE_TARGET_BRANCH, + TOKEN_TYPE_SOURCE_BRANCH, } from '~/vue_shared/components/filtered_search_bar/constants'; -export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { +export default (IssuableTokenKeys, disableBranchFilter = false) => { const reviewerToken = { formattedKey: TOKEN_TITLE_REVIEWER, key: TOKEN_TYPE_REVIEWER, @@ -57,7 +58,7 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { IssuableTokenKeys.tokenKeysWithAlternative.push(draftToken.token); IssuableTokenKeys.conditions.push(...draftToken.conditions); - if (!disableTargetBranchFilter) { + if (!disableBranchFilter) { const targetBranchToken = { formattedKey: __('Target-Branch'), key: TOKEN_TYPE_TARGET_BRANCH, @@ -68,8 +69,18 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { tag: 'branch', }; - IssuableTokenKeys.tokenKeys.push(targetBranchToken); - IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken); + const sourceBranchToken = { + formattedKey: __('Source-Branch'), + key: TOKEN_TYPE_SOURCE_BRANCH, + type: 'string', + param: '', + symbol: '', + icon: 'branch', + tag: 'branch', + }; + + IssuableTokenKeys.tokenKeys.push(targetBranchToken, sourceBranchToken); + IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken, sourceBranchToken); } const approvedToken = { diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js index 892e9130fe8..a1782c549d6 100644 --- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js +++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js @@ -11,6 +11,7 @@ import { TOKEN_TYPE_RELEASE, TOKEN_TYPE_REVIEWER, TOKEN_TYPE_TARGET_BRANCH, + TOKEN_TYPE_SOURCE_BRANCH, } from '~/vue_shared/components/filtered_search_bar/constants'; import DropdownEmoji from './dropdown_emoji'; import DropdownHint from './dropdown_hint'; @@ -157,6 +158,15 @@ export default class AvailableDropdownMappings { }, element: this.container.querySelector('#js-dropdown-target-branch'), }, + [TOKEN_TYPE_SOURCE_BRANCH]: { + reference: null, + gl: DropdownNonUser, + extraArguments: { + endpoint: this.getMergeRequestSourceBranchesEndpoint(), + symbol: '', + }, + element: this.container.querySelector('#js-dropdown-source-branch'), + }, environment: { reference: null, gl: DropdownNonUser, @@ -197,10 +207,17 @@ export default class AvailableDropdownMappings { } getMergeRequestTargetBranchesEndpoint() { - const endpoint = `${ - gon.relative_url_root || '' - }/-/autocomplete/merge_request_target_branches.json`; + const targetBranchEndpointPath = '/-/autocomplete/merge_request_target_branches.json'; + return this.getMergeRequestBranchesEndpoint(targetBranchEndpointPath); + } + getMergeRequestSourceBranchesEndpoint() { + const sourceBranchEndpointPath = '/-/autocomplete/merge_request_source_branches.json'; + return this.getMergeRequestBranchesEndpoint(sourceBranchEndpointPath); + } + + getMergeRequestBranchesEndpoint(endpointPath = '') { + const endpoint = `${gon.relative_url_root || ''}${endpointPath}`; const params = { group_id: this.getGroupId(), project_id: this.getProjectId(), diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index e0cd18a8643..1439a3181b0 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -67,6 +67,7 @@ ], "MemberInterface": [ "GroupMember", + "PendingGroupMember", "ProjectMember" ], "NoteableInterface": [ diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index c9cb1ca14e2..1c2bd10bc81 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -3,16 +3,18 @@ class AutocompleteController < ApplicationController include SearchRateLimitable - skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches] + skip_before_action :authenticate_user!, only: [ + :users, :award_emojis, :merge_request_target_branches, :merge_request_source_branches + ] before_action :check_search_rate_limit!, only: [:users, :projects] feature_category :user_profile, [:users, :user] feature_category :groups_and_projects, [:projects] feature_category :team_planning, [:award_emojis] - feature_category :code_review_workflow, [:merge_request_target_branches] + feature_category :code_review_workflow, [:merge_request_target_branches, :merge_request_source_branches] feature_category :continuous_delivery, [:deploy_keys_with_owners] - urgency :low, [:merge_request_target_branches, :deploy_keys_with_owners, :users] + urgency :low, [:merge_request_target_branches, :merge_request_source_branches, :deploy_keys_with_owners, :users] urgency :low, [:award_emojis] urgency :medium, [:projects] @@ -62,14 +64,11 @@ class AutocompleteController < ApplicationController end def merge_request_target_branches - if target_branch_params.present? - merge_requests = MergeRequestsFinder.new(current_user, target_branch_params).execute - target_branches = merge_requests.recent_target_branches + merge_request_branches(target: true) + end - render json: target_branches.map { |target_branch| { title: target_branch } } - else - render json: { error: _('At least one of group_id or project_id must be specified') }, status: :bad_request - end + def merge_request_source_branches + merge_request_branches(source: true) end def deploy_keys_with_owners @@ -90,7 +89,7 @@ class AutocompleteController < ApplicationController .execute end - def target_branch_params + def branch_params params.permit(:group_id, :project_id).select { |_, v| v.present? } end @@ -98,6 +97,21 @@ class AutocompleteController < ApplicationController def presented_suggested_users [] end + + def merge_request_branches(source: false, target: false) + if branch_params.present? + merge_requests = MergeRequestsFinder.new(current_user, branch_params).execute + + branches = [] + + branches.concat(merge_requests.recent_source_branches) if source + branches.concat(merge_requests.recent_target_branches) if target + + render json: branches.map { |branch| { title: branch } } + else + render json: { error: _('At least one of group_id or project_id must be specified') }, status: :bad_request + end + end end AutocompleteController.prepend_mod_with('AutocompleteController') diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index f7ee90ab870..b7de1c08f86 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -46,6 +46,7 @@ class MergeRequestsFinder < IssuableFinder :merged_before, :reviewer_id, :reviewer_username, + :source_branch, :target_branch, :wip ] @@ -81,7 +82,8 @@ class MergeRequestsFinder < IssuableFinder items = super(items) items = by_negated_reviewer(items) items = by_negated_approved_by(items) - by_negated_target_branch(items) + items = by_negated_target_branch(items) + by_negated_source_branch(items) end private @@ -132,6 +134,12 @@ class MergeRequestsFinder < IssuableFinder items.where.not(target_branch: not_params[:target_branch]) end + + def by_negated_source_branch(items) + return items unless not_params[:source_branch] + + items.where.not(source_branch: not_params[:source_branch]) + end # rubocop: enable CodeReuse/ActiveRecord def by_negated_approved_by(items) diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb index 2745853c9bb..d494c55369d 100644 --- a/app/graphql/types/group_member_type.rb +++ b/app/graphql/types/group_member_type.rb @@ -11,11 +11,11 @@ module Types implements MemberInterface field :group, Types::GroupType, null: true, - description: 'Group that a User is a member of.' + description: 'Group that a user is a member of.' field :notification_email, resolver: Resolvers::GroupMembers::NotificationEmailResolver, - description: "Group notification email for User. Only available for admins." + description: "Group notification email for user. Only available for admins." def group Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a751ed0c94d..524a9b8074b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -529,6 +529,14 @@ class MergeRequest < ApplicationRecord .pluck(:target_branch) end + def self.recent_source_branches(limit: 100) + group(:source_branch) + .select(:source_branch) + .reorder(arel_table[:updated_at].maximum.desc) + .limit(limit) + .pluck(:source_branch) + end + def self.sort_by_attribute(method, excluded_labels: []) case method.to_s when 'merged_at', 'merged_at_asc' then order_merged_at_asc diff --git a/app/validators/ip_cidr_array_validator.rb b/app/validators/ip_cidr_array_validator.rb new file mode 100644 index 00000000000..fff1368508f --- /dev/null +++ b/app/validators/ip_cidr_array_validator.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# IpCidrArrayValidator +# +# Validates that an array of IP are a valid IPv4 or IPv6 CIDR address. +# +# Example: +# +# class Group < ActiveRecord::Base +# validates :ip_array, presence: true, ip_cidr_array: true +# end + +class IpCidrArrayValidator < ActiveModel::EachValidator # rubocop:disable Gitlab/NamespacedClass -- This is a globally shareable validator, but it's unclear what namespace it should belong in + def validate_each(record, attribute, value) + unless value.is_a?(Array) + record.errors.add(attribute, _("must be an array of CIDR values")) + return + end + + value.each do |cidr| + single_validator = IpCidrValidator.new(attributes: attribute) + single_validator.validate_each(record, attribute, cidr) + end + end +end diff --git a/app/validators/ip_cidr_validator.rb b/app/validators/ip_cidr_validator.rb new file mode 100644 index 00000000000..b1760a99d6d --- /dev/null +++ b/app/validators/ip_cidr_validator.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# IpCidrValidator +# +# Validates that an IP is a valid IPv4 or IPv6 CIDR address. +# +# Example: +# +# class Group < ActiveRecord::Base +# validates :ip, presence: true, ip_cidr: true +# end + +class IpCidrValidator < ActiveModel::EachValidator # rubocop:disable Gitlab/NamespacedClass -- This is a globally shareable validator, but it's unclear what namespace it should belong in + def validate_each(record, attribute, value) + # NOTE: We want this to be usable for nullable fields, so we don't validate presence. + # Use a separate `presence` validation for the field if needed. + return true if value.blank? + + # rubocop:disable Layout/LineLength -- The error message is bigger than the line limit + unless valid_cidr_format?(value) + record.errors.add( + attribute, + format(_( + "IP '%{value}' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"), + value: value + ) + ) + return + end + # rubocop:enable Layout/LineLength + + IPAddress.parse(value) + rescue ArgumentError => e + record.errors.add( + attribute, + format(_("IP '%{value}' is not a valid CIDR: %{message}"), value: value, message: e.message) + ) + end + + private + + def valid_cidr_format?(cidr) + cidr.count('/') == 1 && cidr.split('/').last =~ /^\d+$/ + end +end diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 45fd98adbb9..e7fb8d290cd 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -38,7 +38,6 @@ = render 'groups/settings/lfs', f: f = render_if_exists 'groups/settings/code_suggestions', f: f, group: @group = render_if_exists 'groups/settings/experimental_settings', f: f, group: @group - = render_if_exists 'groups/settings/ai_third_party_settings', f: f, group: @group = render 'groups/settings/git_access_protocols', f: f, group: @group = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/subgroup_creation_level', f: f, group: @group diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 86aaa5128a8..52c8a4d4123 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -185,6 +185,11 @@ %li.filter-dropdown-item %button.btn.btn-link.js-data-value.monospace {{title}} + #js-dropdown-source-branch.filtered-search-input-dropdown-menu.dropdown-menu + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + %button.btn.btn-link.js-data-value.monospace + {{title}} #js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item diff --git a/config/feature_flags/development/ai_assist_api.yml b/config/feature_flags/development/ai_assist_api.yml deleted file mode 100644 index 9b7da480f62..00000000000 --- a/config/feature_flags/development/ai_assist_api.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ai_assist_api -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100500 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378470 -milestone: '15.6' -type: development -group: group::incubation -default_enabled: false diff --git a/config/routes.rb b/config/routes.rb index 2c0ae3d0763..925ef0c62e8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,6 +88,7 @@ InitializerConnections.raise_if_new_database_connection do get '/autocomplete/projects' => 'autocomplete#projects' get '/autocomplete/award_emojis' => 'autocomplete#award_emojis' get '/autocomplete/merge_request_target_branches' => 'autocomplete#merge_request_target_branches' + get '/autocomplete/merge_request_source_branches' => 'autocomplete#merge_request_source_branches' get '/autocomplete/deploy_keys_with_owners' => 'autocomplete#deploy_keys_with_owners' Gitlab.ee do diff --git a/db/migrate/20231107062104_add_network_policy_egress_to_agent.rb b/db/migrate/20231107062104_add_network_policy_egress_to_agent.rb new file mode 100644 index 00000000000..c7f9da1831c --- /dev/null +++ b/db/migrate/20231107062104_add_network_policy_egress_to_agent.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AddNetworkPolicyEgressToAgent < Gitlab::Database::Migration[2.2] + milestone '16.6' + + NETWORK_POLICY_EGRESS_DEFAULT = [{ + allow: "0.0.0.0/0", + except: [ + - "10.0.0.0/8", + - "172.16.0.0/12", + - "192.168.0.0/16" + ] + }] + + def change + add_column :remote_development_agent_configs, + :network_policy_egress, + :jsonb, + null: false, + default: NETWORK_POLICY_EGRESS_DEFAULT + end +end diff --git a/db/schema_migrations/20231107062104 b/db/schema_migrations/20231107062104 new file mode 100644 index 00000000000..b58cae8fc66 --- /dev/null +++ b/db/schema_migrations/20231107062104 @@ -0,0 +1 @@ +d31386b36b5db29deb9041febc116915f94fa7c551f1d91d5f474671dccdc709 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index d3faabf540f..5a618d6d636 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -22466,6 +22466,7 @@ CREATE TABLE remote_development_agent_configs ( dns_zone text NOT NULL, network_policy_enabled boolean DEFAULT true NOT NULL, gitlab_workspaces_proxy_namespace text DEFAULT 'gitlab-workspaces'::text NOT NULL, + network_policy_egress jsonb DEFAULT '[{"allow": "0.0.0.0/0", "except": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]}]'::jsonb NOT NULL, CONSTRAINT check_72947a4495 CHECK ((char_length(gitlab_workspaces_proxy_namespace) <= 63)), CONSTRAINT check_9f5cd54d1c CHECK ((char_length(dns_zone) <= 256)) ); diff --git a/doc/administration/settings/jira_cloud_app.md b/doc/administration/settings/jira_cloud_app.md index ef6a23a2984..0b9def383d5 100644 --- a/doc/administration/settings/jira_cloud_app.md +++ b/doc/administration/settings/jira_cloud_app.md @@ -37,6 +37,9 @@ To create an OAuth application on your self-managed instance: - If you're installing the app from the official marketplace listing, enter `https://gitlab.com/-/jira_connect/oauth_callbacks`. - If you're installing the app manually, enter `/-/jira_connect/oauth_callbacks` and replace `` with the URL of your instance. 1. Clear the **Trusted** and **Confidential** checkboxes. + + NOTE: + You must clear these checkboxes to avoid errors. 1. In **Scopes**, select the `api` checkbox only. 1. Select **Save application**. 1. Copy the **Application ID** value. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index b68d068c3ec..1289cc4a329 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11669,6 +11669,29 @@ The edge type for [`PathLock`](#pathlock). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`PathLock`](#pathlock) | The item at the end of the edge. | +#### `PendingGroupMemberConnection` + +The connection type for [`PendingGroupMember`](#pendinggroupmember). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[PendingGroupMemberEdge]`](#pendinggroupmemberedge) | A list of edges. | +| `nodes` | [`[PendingGroupMember]`](#pendinggroupmember) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `PendingGroupMemberEdge` + +The edge type for [`PendingGroupMember`](#pendinggroupmember). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`PendingGroupMember`](#pendinggroupmember) | The item at the end of the edge. | + #### `PipelineArtifactRegistryConnection` The connection type for [`PipelineArtifactRegistry`](#pipelineartifactregistry). @@ -18197,6 +18220,7 @@ GPG signature for a signed commit. | `packageSettings` | [`PackageSettings`](#packagesettings) | Package settings for the namespace. | | `parent` | [`Group`](#group) | Parent group. | | `path` | [`String!`](#string) | Path of the namespace. | +| `pendingMembers` **{warning-solid}** | [`PendingGroupMemberConnection`](#pendinggroupmemberconnection) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. A pending membership of a user within this group. | | `projectCreationLevel` | [`String`](#string) | Permission level required to create projects in the group. | | `recentIssueBoards` | [`BoardConnection`](#boardconnection) | List of recently visited boards of the group. Maximum size is 4. (see [Connections](#connections)) | | `repositorySizeExcessProjectCount` | [`Int!`](#int) | Number of projects in the root namespace where the repository size exceeds the limit. This only applies to namespaces under Project limit enforcement. | @@ -19280,9 +19304,9 @@ Represents a Group Membership. | `createdAt` | [`Time`](#time) | Date and time the membership was created. | | `createdBy` | [`UserCore`](#usercore) | User that authorized membership. | | `expiresAt` | [`Time`](#time) | Date and time the membership expires. | -| `group` | [`Group`](#group) | Group that a User is a member of. | +| `group` | [`Group`](#group) | Group that a user is a member of. | | `id` | [`ID!`](#id) | ID of the member. | -| `notificationEmail` | [`String`](#string) | Group notification email for User. Only available for admins. | +| `notificationEmail` | [`String`](#string) | Group notification email for user. Only available for admins. | | `updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | | `user` | [`UserCore`](#usercore) | User that is associated with the member object. | | `userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. | @@ -22497,6 +22521,46 @@ Represents a file or directory in the project repository that has been locked. | `path` | [`String`](#string) | Locked path. | | `user` | [`UserCore`](#usercore) | User that has locked this path. | +### `PendingGroupMember` + +Represents a Pending Group Membership. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `accessLevel` | [`AccessLevel`](#accesslevel) | GitLab::Access level. | +| `approved` | [`Boolean`](#boolean) | Whether the pending group member has been approved. | +| `avatarUrl` | [`String`](#string) | URL to avatar image file of the pending group member. | +| `createdAt` | [`Time`](#time) | Date and time the membership was created. | +| `createdBy` | [`UserCore`](#usercore) | User that authorized membership. | +| `email` | [`String`](#string) | Public email of the pending group member. | +| `expiresAt` | [`Time`](#time) | Date and time the membership expires. | +| `group` | [`Group`](#group) | Group that a user is a member of. | +| `id` | [`ID!`](#id) | ID of the member. | +| `invited` | [`Boolean`](#boolean) | Whether the pending group member has been invited. | +| `name` | [`String`](#string) | Name of the pending group member. | +| `notificationEmail` | [`String`](#string) | Group notification email for user. Only available for admins. | +| `updatedAt` | [`Time`](#time) | Date and time the membership was last updated. | +| `user` | [`UserCore`](#usercore) | User that is associated with the member object. | +| `userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. | +| `username` | [`String`](#string) | Username of the pending group member. | +| `webUrl` | [`String`](#string) | Web URL of the pending group member. | + +#### Fields with arguments + +##### `PendingGroupMember.mergeRequestInteraction` + +Find a merge request. + +Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. | + ### `Pipeline` #### Fields @@ -31633,6 +31697,7 @@ Implementations: Implementations: - [`GroupMember`](#groupmember) +- [`PendingGroupMember`](#pendinggroupmember) - [`ProjectMember`](#projectmember) ##### Fields diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index ceca0eefe5b..cd23b903d30 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -140,9 +140,9 @@ as it can cause the pipeline to behave unexpectedly. | `GITLAB_CI` | all | all | Available for all jobs executed in CI/CD. `true` when available. | | `GITLAB_FEATURES` | 10.6 | all | The comma-separated list of licensed features available for the GitLab instance and license. | | `GITLAB_USER_EMAIL` | 8.12 | all | The email of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the email of the user who started the job. | -| `GITLAB_USER_ID` | 8.12 | all | The ID of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the ID of the user who started the job. | +| `GITLAB_USER_ID` | 8.12 | all | The numeric ID of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the ID of the user who started the job. | | `GITLAB_USER_LOGIN` | 10.0 | all | The username of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the username of the user who started the job. | -| `GITLAB_USER_NAME` | 10.0 | all | The name of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the name of the user who started the job. | +| `GITLAB_USER_NAME` | 10.0 | all | The display name of the user who started the pipeline, unless the job is a manual job. In manual jobs, the value is the name of the user who started the job. | | `KUBECONFIG` | 14.2 | all | The path to the `kubeconfig` file with contexts for every shared agent connection. Only available when a [GitLab agent is authorized to access the project](../../user/clusters/agent/ci_cd_workflow.md#authorize-the-agent). | | `TRIGGER_PAYLOAD` | 13.9 | all | The webhook payload. Only available when a pipeline is [triggered with a webhook](../triggers/index.md#access-webhook-payload). | @@ -157,8 +157,8 @@ These variables are available when: |---------------------------------------------|--------|--------|-------------| | `CI_MERGE_REQUEST_APPROVED` | 14.1 | all | Approval status of the merge request. `true` when [merge request approvals](../../user/project/merge_requests/approvals/index.md) is available and the merge request has been approved. | | `CI_MERGE_REQUEST_ASSIGNEES` | 11.9 | all | Comma-separated list of usernames of assignees for the merge request. | -| `CI_MERGE_REQUEST_ID` | 11.6 | all | The instance-level ID of the merge request. This is a unique ID across all projects on GitLab. | -| `CI_MERGE_REQUEST_IID` | 11.6 | all | The project-level IID (internal ID) of the merge request. This ID is unique for the current project. | +| `CI_MERGE_REQUEST_ID` | 11.6 | all | The instance-level ID of the merge request. This is a unique ID across all projects on the GitLab instance. | +| `CI_MERGE_REQUEST_IID` | 11.6 | all | The project-level IID (internal ID) of the merge request. This ID is unique for the current project, and is the number used in the merge request URL, page title, and other visible locations. | | `CI_MERGE_REQUEST_LABELS` | 11.9 | all | Comma-separated label names of the merge request. | | `CI_MERGE_REQUEST_MILESTONE` | 11.9 | all | The milestone title of the merge request. | | `CI_MERGE_REQUEST_PROJECT_ID` | 11.6 | all | The ID of the project of the merge request. | diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md index fb947f93068..8c90ae6cc85 100644 --- a/doc/development/ai_features/index.md +++ b/doc/development/ai_features/index.md @@ -36,7 +36,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Apply the following two feature flags to any AI feature work: -- A general that applies to all AI features. +- A general flag (`ai_global_switch`) that applies to all AI features. - A flag specific to that feature. The feature flag name [must be different](../feature_flags/index.md#feature-flags-for-licensed-features) than the licensed feature name. See the [feature flag tracker](https://gitlab.com/gitlab-org/gitlab/-/issues/405161) for the list of all feature flags and how to use them. @@ -63,11 +63,10 @@ Use [this snippet](https://gitlab.com/gitlab-org/gitlab/-/snippets/2554994) for 1. Ensure you have followed [the process to obtain an EE license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee-developer-licenses) for your local instance 1. Simulate the GDK to [simulate SaaS](../ee_features.md#simulate-a-saas-instance) and ensure the group you want to test has an Ultimate license -1. Enable `Experimental features` and `Third-party AI services` +1. Enable `Experimental features`: 1. Go to the group with the Ultimate license 1. **Group Settings** > **General** -> **Permissions and group features** 1. Enable **Experiment features** - 1. Enable **Third-party AI services** 1. Enable the specific feature flag for the feature you want to test 1. Set the required access token. To receive an access token: 1. For Vertex, follow the [instructions below](#configure-gcp-vertex-access). @@ -392,11 +391,11 @@ end We recommend to use [policies](../policies.md) to deal with authorization for a feature. Currently we need to make sure to cover the following checks: -1. General AI feature flag is enabled +1. General AI feature flag (`ai_global_switch`) is enabled 1. Feature specific feature flag is enabled 1. The namespace has the required license for the feature 1. User is a member of the group/project -1. `experiment_features_enabled` and `third_party_ai_features_enabled` flags are set on the `Namespace` +1. `experiment_features_enabled` settings are set on the `Namespace` For our example, we need to implement the `allowed?(:amazing_new_ai_feature)` call. As an example, you can look at the [Issue Policy for the summarize comments feature](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/issue_policy.rb). In our example case, we want to implement the feature for Issues as well: @@ -474,10 +473,9 @@ Caching has following limitations: ### Check if feature is allowed for this resource based on namespace settings -There are two settings allowed on root namespace level that restrict the use of AI features: +There is one setting allowed on root namespace level that restrict the use of AI features: - `experiment_features_enabled` -- `third_party_ai_features_enabled`. To check if that feature is allowed for a given namespace, call: @@ -485,13 +483,12 @@ To check if that feature is allowed for a given namespace, call: Gitlab::Llm::StageCheck.available?(namespace, :name_of_the_feature) ``` -Add the name of the feature to the `Gitlab::Llm::StageCheck` class. There are arrays there that differentiate -between experimental and beta features. +Add the name of the feature to the `Gitlab::Llm::StageCheck` class. There are +arrays there that differentiate between experimental and beta features. This way we are ready for the following different cases: -- If the feature is not in any array, the check will return `true`. For example, the feature was moved to GA and does not use a third-party setting. -- If feature is in GA, but uses a third-party setting, the class will return a proper answer based on the namespace third-party setting. +- If the feature is not in any array, the check will return `true`. For example, the feature was moved to GA. To move the feature from the experimental phase to the beta phase, move the name of the feature from the `EXPERIMENTAL_FEATURES` array to the `BETA_FEATURES` array. diff --git a/doc/user/ai_features.md b/doc/user/ai_features.md index 685f1dbd99b..87fcc924c51 100644 --- a/doc/user/ai_features.md +++ b/doc/user/ai_features.md @@ -28,22 +28,12 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea ## Enable AI/ML features -- Third-party AI features - - All features built on large language models (LLM) from Google, - Anthropic or OpenAI (besides Code Suggestions) require that this setting is - enabled at the group level. - - [Generally Available](../policy/experiment-beta-support.md#generally-available-ga) - features are available when third-party AI features are enabled. - - Third-party AI features are enabled by default. - - This setting is available to Ultimate groups on SaaS and can be - set by a user who has the Owner role in the group. - - View [how to enable this setting](group/manage.md#enable-third-party-ai-features). - Experiment and Beta features - All features categorized as [Experiment features](../policy/experiment-beta-support.md#experiment) or [Beta features](../policy/experiment-beta-support.md#beta) (besides Code Suggestions) require that this setting is enabled at the group - level. This is in addition to the Third-party AI features setting. + level. - Their usage is subject to the [Testing Terms of Use](https://about.gitlab.com/handbook/legal/testing-agreement/). - Experiment and Beta features are disabled by default. @@ -65,7 +55,6 @@ The following subsections describe the experimental AI features in more detail. To use this feature: - The parent group of the project must: - - Enable the [third-party AI features setting](group/manage.md#enable-third-party-ai-features). - Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features). - You must be a member of the project with sufficient permissions to view the repository. @@ -111,7 +100,6 @@ We cannot guarantee that the large language model produces results that are corr To use this feature: - The parent group of the issue must: - - Enable the [third-party AI features setting](group/manage.md#enable-third-party-ai-features). - Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features). - You must be a member of the project with sufficient permissions to view the issue. @@ -135,7 +123,6 @@ language model referenced above. To use this feature: - The parent group of the project must: - - Enable the [third-party AI features setting](group/manage.md#enable-third-party-ai-features). - Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features). - You must be a member of the project with sufficient permissions to view the CI/CD analytics. @@ -161,7 +148,6 @@ Provide feedback on this experimental feature in [issue 416833](https://gitlab.c To use this feature: - The parent group of the project must: - - Enable the [third-party AI features setting](group/manage.md#enable-third-party-ai-features). - Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features). - You must be a member of the project with sufficient permissions to view the CI/CD job. @@ -176,7 +162,6 @@ reason for the failure. To use this feature: - The parent group of the project must: - - Enable the [third-party AI features setting](group/manage.md#enable-third-party-ai-features). - Enable the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features). - You must be a member of the project with sufficient permissions to view the issue. @@ -215,8 +200,6 @@ These features are in a variety of [feature support levels](../policy/experiment Some AI features require the use of third-party AI services models and APIs from: Google AI and OpenAI. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use to provide these features. -Group owners can control which top-level groups have access to third-party AI features by using the [group level third-party AI features setting](group/manage.md#enable-third-party-ai-features). - ### Model accuracy and quality Generative AI may produce unexpected results that may be: diff --git a/doc/user/gitlab_duo_chat.md b/doc/user/gitlab_duo_chat.md index f12d9bfca92..c103d9c29ba 100644 --- a/doc/user/gitlab_duo_chat.md +++ b/doc/user/gitlab_duo_chat.md @@ -44,7 +44,6 @@ This is an experimental feature and we're continuously extending the capabilitie To use this feature, at least one group you're a member of must: -- Have the [third-party AI features setting](group/manage.md#enable-third-party-ai-features) enabled. - Have the [experiment and beta features setting](group/manage.md#enable-experiment-and-beta-features) enabled. ## Use GitLab Duo Chat diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index 5a2f6d32154..48f86ee4f0e 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -492,29 +492,6 @@ To enable Experiment features for a top-level group: 1. Under **Experiment and Beta features**, select the **Use Experiment and Beta features** checkbox. 1. Select **Save changes**. -## Enable third-party AI features **(ULTIMATE SAAS)** - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222) in GitLab 16.0. - -WARNING: -These AI features use [third-party services](../ai_features.md#data-usage) -and require transmission of data, including personal data. - -All users in the group have third-party AI features enabled by default. -This setting [cascades to all projects](../project/merge_requests/approvals/settings.md#settings-cascading) -that belong to the group. - -To disable third-party AI features for a group: - -1. On the left sidebar, select **Search or go to** and find your group. -1. Select **Settings > General**. -1. Expand **Permissions and group features**. -1. Under **Third-party AI services**, uncheck the **Use third-party AI services** checkbox. -1. Select **Save changes**. - -When Code Suggestions are enabled and disabled, an -[audit event](../../administration/audit_events.md) is created. - ## Group activity analytics **(PREMIUM ALL)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207164) in GitLab 12.10 as a [Beta feature](../../policy/experiment-beta-support.md#beta). diff --git a/doc/user/packages/container_registry/troubleshoot_container_registry.md b/doc/user/packages/container_registry/troubleshoot_container_registry.md index 13e14dfdeb4..acef00640ef 100644 --- a/doc/user/packages/container_registry/troubleshoot_container_registry.md +++ b/doc/user/packages/container_registry/troubleshoot_container_registry.md @@ -128,6 +128,15 @@ time is set to 15 minutes. If you are using self-managed GitLab, an administrator can [increase the token duration](../../../administration/packages/container_registry.md#increase-token-duration). +## `insufficient_scope: authorization failed` when pulling an image + +GitLab CI/CD jobs that set [`image`](../../../ci/yaml/index.md#image) to pull an image +from a project's container registry automatically authenticate with a [CI/CD job token](../../../ci/jobs/ci_job_token.md). + +All projects with CI/CD jobs that fetch images from the container registry must be listed +in the registry project's [job token allowlist](../../../ci/jobs/ci_job_token.md#allow-access-to-your-project-with-a-job-token). +Otherwise, the job fails with an `insufficient_scope: authorization failed` error. + ## Slow uploads when using `kaniko` to push large images When you push large images with `kaniko`, you might experience uncharacteristically long delays. diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md index 2c395538455..c8730c42022 100644 --- a/doc/user/packages/maven_repository/index.md +++ b/doc/user/packages/maven_repository/index.md @@ -32,6 +32,10 @@ Do not use authentication methods other than the methods documented here. Undocu #### Edit the client configuration +Update your configuration to authenticate to the Maven repository with HTTP. + +##### Custom HTTP header + You must add the authentication details to the configuration file for your client. @@ -127,6 +131,97 @@ file: } ``` +::EndTabs + +##### Basic HTTP Authentication + +You can also use basic HTTP authentication to authenticate to the Maven Package Registry. + +::Tabs + +:::TabTitle `mvn` + +| Token type | Name must be | Token | +| --------------------- | ---------------------------- | ---------------------------------------------------------------------- | +| Personal access token | The username of the user | Paste token as-is, or define an environment variable to hold the token | +| Deploy token | The username of deploy token | Paste token as-is, or define an environment variable to hold the token | +| CI Job token | `gitlab-ci-token` | `${CI_JOB_TOKEN}` | + +Add the following section to your +[`settings.xml`](https://maven.apache.org/settings.html) file. + +```xml + + + + gitlab-maven + REPLACE_WITH_NAME + REPLACE_WITH_TOKEN + + + REPLACE_WITH_NAME + REPLACE_WITH_TOKEN + + + + + +``` + +:::TabTitle `gradle` + +| Token type | Name must be | Token | +| --------------------- | ---------------------------- | ---------------------------------------------------------------------- | +| Personal access token | The username of the user | Paste token as-is, or define an environment variable to hold the token | +| Deploy token | The username of deploy token | Paste token as-is, or define an environment variable to hold the token | +| CI Job token | `gitlab-ci-token` | `System.getenv("CI_JOB_TOKEN")` | + +In [your `GRADLE_USER_HOME` directory](https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home), +create a file `gradle.properties` with the following content: + +```properties +gitLabPrivateToken=REPLACE_WITH_YOUR_TOKEN +``` + +Add a `repositories` section to your +[`build.gradle`](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html). + +- In Groovy DSL: + + ```groovy + repositories { + maven { + url "https://gitlab.example.com/api/v4/groups//-/packages/maven" + name "GitLab" + credentials(PasswordCredentials) { + username = 'REPLACE_WITH_NAME' + password = gitLabPrivateToken + } + authentication { + basic(BasicAuthentication) + } + } + } + ``` + +- In Kotlin DSL: + + ```kotlin + repositories { + maven { + url = uri("https://gitlab.example.com/api/v4/groups//-/packages/maven") + name = "GitLab" + credentials(BasicAuthentication::class) { + username = "REPLACE_WITH_NAME" + password = findProperty("gitLabPrivateToken") as String? + } + authentication { + create("basic", BasicAuthentication::class) + } + } + } + ``` + :::TabTitle `sbt` | Token type | Name must be | Token | diff --git a/doc/user/project/merge_requests/ai_in_merge_requests.md b/doc/user/project/merge_requests/ai_in_merge_requests.md index c29060bf44b..2b4b28dafa2 100644 --- a/doc/user/project/merge_requests/ai_in_merge_requests.md +++ b/doc/user/project/merge_requests/ai_in_merge_requests.md @@ -14,7 +14,7 @@ Additional information on enabling these features and maturity can be found in o > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10591) in GitLab 16.3 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com that is using Google's Vertex service and the `text-bison` model. It requires the [group-level third-party AI features setting](../../group/manage.md#enable-third-party-ai-features) to be enabled. +This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com. Merge requests in projects often have [templates](../description_templates.md#create-a-merge-request-template) defined that need to be filled out. This helps reviewers and other users understand the purpose and changes a merge request might propose. @@ -40,7 +40,7 @@ Provide feedback on this experimental feature in [issue 416537](https://gitlab.c > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10401) in GitLab 16.2 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com that is using Google's Vertex service and the `text-bison` model. It requires the [group-level third-party AI features setting](../../group/manage.md#enable-third-party-ai-features) to be enabled. +This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com. GitLab Duo Merge request summaries are available on the merge request page in: @@ -56,7 +56,7 @@ Provide feedback on this experimental feature in [issue 408726](https://gitlab.c > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10466) in GitLab 16.0 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com that is using Google's Vertex service and the `text-bison` model. It requires the [group-level third-party AI features setting](../../group/manage.md#enable-third-party-ai-features) to be enabled. +This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com. When you've completed your review of a merge request and are ready to [submit your review](reviews/index.md#submit-a-review), generate a GitLab Duo Code review summary: @@ -78,7 +78,7 @@ Provide feedback on this experimental feature in [issue 408991](https://gitlab.c > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10453) in GitLab 16.2 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com that is using Google's Vertex service and the `text-bison` model. It requires the [group-level third-party AI features setting](../../group/manage.md#enable-third-party-ai-features) to be enabled. +This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com. When preparing to merge your merge request you may wish to edit the proposed squash or merge commit message. @@ -99,7 +99,7 @@ Provide feedback on this experimental feature in [issue 408994](https://gitlab.c > [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10366) in GitLab 16.0 as an [Experiment](../../../policy/experiment-beta-support.md#experiment). -This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com that is using Google's Vertex service and the `code-bison` model. It requires the [group-level third-party AI features setting](../../group/manage.md#enable-third-party-ai-features) to be enabled. +This feature is an [Experiment](../../../policy/experiment-beta-support.md) on GitLab.com. Use GitLab Duo Test generation in a merge request to see a list of suggested tests for the file you are reviewing. This functionality can help determine if appropriate test coverage has been provided, or if you need more coverage for your project. diff --git a/doc/user/project/repository/code_suggestions/troubleshooting.md b/doc/user/project/repository/code_suggestions/troubleshooting.md index 2faf20b3035..86400ea8860 100644 --- a/doc/user/project/repository/code_suggestions/troubleshooting.md +++ b/doc/user/project/repository/code_suggestions/troubleshooting.md @@ -18,9 +18,6 @@ In GitLab, ensure Code Suggestions is enabled: - [For your user account](../../../profile/preferences.md#enable-code-suggestions). - [For *all* top-level groups your account belongs to](../../../group/manage.md#enable-code-suggestions). If you don't have a role that lets you view the top-level group's settings, contact a group owner. -To confirm that your account is enabled, go to [https://gitlab.com/api/v4/ml/ai-assist](https://gitlab.com/api/v4/ml/ai-assist). The `user_is_allowed` key should have should have a value of `true`. -A `404 Not Found` result is returned if either of the previous conditions is not met. - ### Code Suggestions not displayed in VS Code or GitLab WebIDE Check all the steps in [Code Suggestions aren't displayed](#code-suggestions-arent-displayed) first. diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 517de98a148..14c3fccee32 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -228,7 +228,7 @@ module API requires :path, type: String, desc: 'Package path', documentation: { example: 'foo/bar/mypkg/1.0-SNAPSHOT' } requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex, documentation: { example: 'mypkg-1.0-SNAPSHOT.pom' } end - route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true + route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true, basic_auth_personal_access_token: true put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do authorize_upload! @@ -254,7 +254,7 @@ module API requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex, documentation: { example: 'mypkg-1.0-SNAPSHOT.pom' } requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end - route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true + route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true, basic_auth_personal_access_token: true put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do unprocessable_entity! if Gitlab::FIPS.enabled? && params[:file].md5 authorize_upload! diff --git a/lib/gitlab/redis/multi_store.rb b/lib/gitlab/redis/multi_store.rb index f5576d7fe20..6acbf83df24 100644 --- a/lib/gitlab/redis/multi_store.rb +++ b/lib/gitlab/redis/multi_store.rb @@ -277,10 +277,13 @@ module Gitlab # # Let's define it explicitly instead of propagating it to method_missing def close - if use_primary_and_secondary_stores? - [primary_store, secondary_store].map(&:close).first + if same_redis_store? + # if same_redis_store?, `use_primary_store_as_default?` returns false + # but we should avoid a feature-flag check in `.close` to avoid checking out + # an ActiveRecord connection during clean up. + secondary_store.close else - default_store.close + [primary_store, secondary_store].map(&:close).first end end diff --git a/lib/gitlab/sidekiq_middleware/server_metrics.rb b/lib/gitlab/sidekiq_middleware/server_metrics.rb index a8b3683e09f..37a9ed37891 100644 --- a/lib/gitlab/sidekiq_middleware/server_metrics.rb +++ b/lib/gitlab/sidekiq_middleware/server_metrics.rb @@ -25,11 +25,6 @@ module Gitlab def metrics metrics = { - sidekiq_jobs_cpu_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_cpu_seconds, 'Seconds this Sidekiq job spent on the CPU', {}, SIDEKIQ_LATENCY_BUCKETS), - sidekiq_jobs_db_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_db_seconds, 'Seconds of database time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS), - sidekiq_jobs_gitaly_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_gitaly_seconds, 'Seconds of Gitaly time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS), - sidekiq_redis_requests_duration_seconds: ::Gitlab::Metrics.histogram(:sidekiq_redis_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent requests a Redis server', {}, Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS), - sidekiq_elasticsearch_requests_duration_seconds: ::Gitlab::Metrics.histogram(:sidekiq_elasticsearch_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent in requests to an Elasticsearch server', {}, SIDEKIQ_LATENCY_BUCKETS), sidekiq_jobs_retried_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_retried_total, 'Sidekiq jobs retried'), sidekiq_jobs_interrupted_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_interrupted_total, 'Sidekiq jobs interrupted'), sidekiq_redis_requests_total: ::Gitlab::Metrics.counter(:sidekiq_redis_requests_total, 'Redis requests during a Sidekiq job execution'), @@ -43,9 +38,24 @@ module Gitlab metrics[:sidekiq_jobs_completion_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete Sidekiq job', {}, SIDEKIQ_JOB_DURATION_BUCKETS) metrics[:sidekiq_jobs_queue_duration_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_queue_duration_seconds, 'Duration in seconds that a Sidekiq job was queued before being executed', {}, SIDEKIQ_QUEUE_DURATION_BUCKETS) metrics[:sidekiq_jobs_failed_total] = ::Gitlab::Metrics.counter(:sidekiq_jobs_failed_total, 'Sidekiq jobs failed') + + # resource usage + metrics[:sidekiq_jobs_cpu_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_cpu_seconds, 'Seconds this Sidekiq job spent on the CPU', {}, SIDEKIQ_LATENCY_BUCKETS) + metrics[:sidekiq_jobs_db_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_db_seconds, 'Seconds of database time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS) + metrics[:sidekiq_jobs_gitaly_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_jobs_gitaly_seconds, 'Seconds of Gitaly time to run Sidekiq job', {}, SIDEKIQ_LATENCY_BUCKETS) + metrics[:sidekiq_redis_requests_duration_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_redis_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent in requests to a Redis server', {}, Gitlab::Instrumentation::Redis::QUERY_TIME_BUCKETS) + metrics[:sidekiq_elasticsearch_requests_duration_seconds] = ::Gitlab::Metrics.histogram(:sidekiq_elasticsearch_requests_duration_seconds, 'Duration in seconds that a Sidekiq job spent in requests to an Elasticsearch server', {}, SIDEKIQ_LATENCY_BUCKETS) else - # The sum metric is still used in GitLab.com for dashboards + # These metrics are used in GitLab.com dashboards metrics[:sidekiq_jobs_completion_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_jobs_completion_seconds_sum, 'Total of seconds to complete Sidekiq job') + metrics[:sidekiq_jobs_completion_count] = ::Gitlab::Metrics.counter(:sidekiq_jobs_completion_count, 'Number of Sidekiq jobs completed') + + # resource usage sums + metrics[:sidekiq_jobs_cpu_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_jobs_cpu_seconds_sum, 'Total seconds this Sidekiq job spent on the CPU') + metrics[:sidekiq_jobs_db_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_jobs_db_seconds_sum, 'Total seconds of database time to run Sidekiq job') + metrics[:sidekiq_jobs_gitaly_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_jobs_gitaly_seconds_sum, 'Total seconds Gitaly time to run Sidekiq job') + metrics[:sidekiq_redis_requests_duration_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_redis_requests_duration_seconds_sum, 'Total duration in seconds that a Sidekiq job spent in requests to a Redis server') + metrics[:sidekiq_elasticsearch_requests_duration_seconds_sum] = ::Gitlab::Metrics.counter(:sidekiq_elasticsearch_requests_duration_seconds_sum, 'Total duration in seconds that a Sidekiq job spent in requests to an Elasticsearch server') end metrics @@ -89,8 +99,9 @@ module Gitlab # in metrics and can use them in the `ThreadsSampler` for setting a label Thread.current.name ||= Gitlab::Metrics::Samplers::ThreadsSampler::SIDEKIQ_WORKER_THREAD_NAME - labels = create_labels(worker.class, queue, job) - instrument(job, labels) do + @job = job + @labels = create_labels(worker.class, queue, job) + instrument do yield end end @@ -99,8 +110,8 @@ module Gitlab attr_reader :metrics - def instrument(job, labels) - queue_duration = ::Gitlab::InstrumentationHelper.queue_duration_for_job(job) + def instrument + @queue_duration = ::Gitlab::InstrumentationHelper.queue_duration_for_job(job) @metrics[:sidekiq_jobs_queue_duration_seconds]&.observe(labels, queue_duration) if queue_duration @@ -114,43 +125,33 @@ module Gitlab @metrics[:sidekiq_jobs_interrupted_total].increment(labels, 1) end - job_succeeded = false + @job_succeeded = false monotonic_time_start = Gitlab::Metrics::System.monotonic_time job_thread_cputime_start = get_thread_cputime begin transaction = Gitlab::Metrics::BackgroundTransaction.new transaction.run { yield } - job_succeeded = true + @job_succeeded = true ensure monotonic_time_end = Gitlab::Metrics::System.monotonic_time job_thread_cputime_end = get_thread_cputime - monotonic_time = monotonic_time_end - monotonic_time_start - job_thread_cputime = job_thread_cputime_end - job_thread_cputime_start + @monotonic_time = monotonic_time_end - monotonic_time_start + @job_thread_cputime = job_thread_cputime_end - job_thread_cputime_start - # sidekiq_running_jobs, sidekiq_jobs_failed_total should not include the job_status label @metrics[:sidekiq_running_jobs].increment(labels, -1) - if Feature.enabled?(:emit_sidekiq_histogram_metrics, type: :ops) - @metrics[:sidekiq_jobs_failed_total].increment(labels, 1) unless job_succeeded - else - # we don't need job_status label here - @metrics[:sidekiq_jobs_completion_seconds_sum].increment(labels, monotonic_time) - end + @instrumentation = job[:instrumentation] || {} + + record_resource_usage_counters # job_status: done, fail match the job_status attribute in structured logging labels[:job_status] = job_succeeded ? "done" : "fail" - instrumentation = job[:instrumentation] || {} - @metrics[:sidekiq_jobs_cpu_seconds].observe(labels, job_thread_cputime) - @metrics[:sidekiq_jobs_completion_seconds]&.observe(labels, monotonic_time) + record_histograms - @metrics[:sidekiq_jobs_db_seconds].observe(labels, ActiveRecord::LogSubscriber.runtime / 1000) - @metrics[:sidekiq_jobs_gitaly_seconds].observe(labels, get_gitaly_time(instrumentation)) @metrics[:sidekiq_redis_requests_total].increment(labels, get_redis_calls(instrumentation)) - @metrics[:sidekiq_redis_requests_duration_seconds].observe(labels, get_redis_time(instrumentation)) @metrics[:sidekiq_elasticsearch_requests_total].increment(labels, get_elasticsearch_calls(instrumentation)) - @metrics[:sidekiq_elasticsearch_requests_duration_seconds].observe(labels, get_elasticsearch_time(instrumentation)) @metrics[:sidekiq_mem_total_bytes].set(labels, get_thread_memory_total_allocations(instrumentation)) with_load_balancing_settings(job) do |settings| @@ -162,15 +163,50 @@ module Gitlab @metrics[:sidekiq_load_balancing_count].increment(labels.merge(load_balancing_labels), 1) end - sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS) - Gitlab::Metrics::SidekiqSlis.record_execution_apdex(sli_labels, monotonic_time) if job_succeeded - Gitlab::Metrics::SidekiqSlis.record_execution_error(sli_labels, !job_succeeded) - Gitlab::Metrics::SidekiqSlis.record_queueing_apdex(sli_labels, queue_duration) if queue_duration + @sli_labels = labels.slice(*SIDEKIQ_SLI_LABELS) + record_execution_sli + record_queueing_sli end end private + attr_reader :labels, :job, :queue_duration, :job_succeeded, :monotonic_time, :job_thread_cputime, :instrumentation, :sli_labels + + def record_resource_usage_counters + if Feature.enabled?(:emit_sidekiq_histogram_metrics, type: :ops) + @metrics[:sidekiq_jobs_failed_total].increment(labels, 1) unless job_succeeded + else + @metrics[:sidekiq_jobs_completion_seconds_sum].increment(labels, monotonic_time) + @metrics[:sidekiq_jobs_completion_count].increment(labels, 1) + @metrics[:sidekiq_jobs_cpu_seconds_sum].increment(labels, job_thread_cputime) + @metrics[:sidekiq_jobs_db_seconds_sum].increment(labels, ActiveRecord::LogSubscriber.runtime / 1000) + @metrics[:sidekiq_jobs_gitaly_seconds_sum].increment(labels, get_gitaly_time(instrumentation)) + @metrics[:sidekiq_redis_requests_duration_seconds_sum].increment(labels, get_redis_time(instrumentation)) + @metrics[:sidekiq_elasticsearch_requests_duration_seconds_sum].increment(labels, get_elasticsearch_time(instrumentation)) + end + end + + def record_histograms + @metrics[:sidekiq_jobs_cpu_seconds]&.observe(labels, job_thread_cputime) + + @metrics[:sidekiq_jobs_completion_seconds]&.observe(labels, monotonic_time) + + @metrics[:sidekiq_jobs_db_seconds]&.observe(labels, ActiveRecord::LogSubscriber.runtime / 1000) + @metrics[:sidekiq_jobs_gitaly_seconds]&.observe(labels, get_gitaly_time(instrumentation)) + @metrics[:sidekiq_redis_requests_duration_seconds]&.observe(labels, get_redis_time(instrumentation)) + @metrics[:sidekiq_elasticsearch_requests_duration_seconds]&.observe(labels, get_elasticsearch_time(instrumentation)) + end + + def record_queueing_sli + Gitlab::Metrics::SidekiqSlis.record_queueing_apdex(sli_labels, queue_duration) if queue_duration + end + + def record_execution_sli + Gitlab::Metrics::SidekiqSlis.record_execution_apdex(sli_labels, monotonic_time) if job_succeeded + Gitlab::Metrics::SidekiqSlis.record_execution_error(sli_labels, !job_succeeded) + end + def with_load_balancing_settings(job) keys = %w[load_balancing_strategy worker_data_consistency] return unless keys.all? { |k| job.key?(k) } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e4ed3e83e59..1a0a5c3df73 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1375,6 +1375,12 @@ msgstr "" msgid "'%{value}' days of inactivity must be greater than or equal to 90" msgstr "" +msgid "'allow: %{allow}' must be a string" +msgstr "" + +msgid "'except: %{except}' must be an array of string" +msgstr "" + msgid "'projects' is not yet supported" msgstr "" @@ -1915,9 +1921,6 @@ msgstr "" msgid "AISummary|View summary" msgstr "" -msgid "AI| %{link_start}How is my data used?%{link_end}" -msgstr "" - msgid "AI|%{tool} is %{transition} an answer" msgstr "" @@ -1960,9 +1963,6 @@ msgstr "" msgid "AI|Explain your rating to help us improve! (optional)" msgstr "" -msgid "AI|Features that use third-party AI services require transmission of data, including personal data." -msgstr "" - msgid "AI|For example: Organizations should be able to forecast into the future by using value stream analytics charts. This feature would help them understand how their metrics are trending." msgstr "" @@ -2037,18 +2037,12 @@ msgstr "" msgid "AI|There is too much text in the chat. Please try again with a shorter text." msgstr "" -msgid "AI|Third-party AI services" -msgstr "" - msgid "AI|To help improve the quality of the content, send your feedback to GitLab team members." msgstr "" msgid "AI|Unhelpful" msgstr "" -msgid "AI|Use third-party AI services" -msgstr "" - msgid "AI|What does the selected code mean?" msgstr "" @@ -11922,7 +11916,7 @@ msgstr "" msgid "CodeSuggestions|Projects in this group can use Code Suggestions" msgstr "" -msgid "CodeSuggestions|Subject to the %{terms_link_start}Testing Terms of Use%{link_end}. Code Suggestions currently uses third-party AI services unless those are %{third_party_features_link_start}disabled%{link_end}." +msgid "CodeSuggestions|Subject to the %{terms_link_start}Testing Terms of Use%{link_end}. Code Suggestions uses third-party AI services." msgstr "" msgid "CodeownersValidation|An error occurred while loading the validation errors. Please try again later." @@ -24209,6 +24203,12 @@ msgstr "" msgid "INFO: Your SSH key is expiring soon. Please generate a new key." msgstr "" +msgid "IP '%{value}' is not a valid CIDR: %{message}" +msgstr "" + +msgid "IP '%{value}' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')" +msgstr "" + msgid "IP Address" msgstr "" @@ -45951,6 +45951,9 @@ msgstr "" msgid "Source project cannot be found." msgstr "" +msgid "Source-Branch" +msgstr "" + msgid "SourceEditor|\"el\" parameter is required for createInstance()" msgstr "" @@ -48816,9 +48819,6 @@ msgstr "" msgid "Third Party Advisory Link" msgstr "" -msgid "Third party AI settings not allowed." -msgstr "" - msgid "This %{issuableDisplayName} is locked. Only project members can comment." msgstr "" @@ -57594,6 +57594,18 @@ msgstr "" msgid "must be after start" msgstr "" +msgid "must be an array" +msgstr "" + +msgid "must be an array of CIDR values" +msgstr "" + +msgid "must be an array of hash" +msgstr "" + +msgid "must be an array of hash containing 'allow' attribute of type string" +msgstr "" + msgid "must be an email you have verified" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb index 25a289f4e19..f794edb1b7a 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb @@ -3,7 +3,13 @@ module QA RSpec.describe 'Create', product_group: :source_code do describe 'Push mirror a repository over HTTP' do - it 'configures and syncs LFS objects for a (push) mirrored repository', :aggregate_failures, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347847' do + it 'configures and syncs LFS objects for a (push) mirrored repository', :aggregate_failures, + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347847', + quarantine: { + only: { condition: -> { ENV['QA_RUN_TYPE'] == 'e2e-package-and-test-ce' } }, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/412268', + type: :investigating + } do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index e430781412d..6704c05ead8 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -3,7 +3,14 @@ module QA RSpec.describe 'Create' do describe 'Push mirror a repository over HTTP', product_group: :source_code do - it 'configures and syncs a (push) mirrored repository', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347741' do + it('configures and syncs a (push) mirrored repository', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347741', + quarantine: { + only: { condition: -> { ENV['QA_RUN_TYPE'] == 'e2e-package-and-test-ce' } }, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/412611', + type: :investigating + } + ) do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index a66cb4364d7..cbe0319a78d 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -454,77 +454,85 @@ RSpec.describe AutocompleteController do end end - context 'Get merge_request_target_branches', feature_category: :code_review_workflow do - let!(:merge_request) { create(:merge_request, source_project: project, target_branch: 'feature') } - - context 'anonymous user' do - it 'returns empty json' do - get :merge_request_target_branches, params: { project_id: project.id } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_empty - end + context 'GET branches', feature_category: :code_review_workflow do + let_it_be(:merge_request) do + create(:merge_request, source_project: project, + source_branch: 'test_source_branch', target_branch: 'test_target_branch') end - context 'user without any accessible merge requests' do - it 'returns empty json' do - sign_in(create(:user)) + shared_examples 'Get merge_request_{}_branches' do |path, expected_result| + context 'anonymous user' do + it 'returns empty json' do + get path, params: { project_id: project.id } - get :merge_request_target_branches, params: { project_id: project.id } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to be_empty + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + end end - end - context 'user with an accessible merge request but no scope' do - where( - params: [ - {}, - { group_id: ' ' }, - { project_id: ' ' }, - { group_id: ' ', project_id: ' ' } - ] - ) + context 'user without any accessible merge requests' do + it 'returns empty json' do + sign_in(create(:user)) - with_them do - it 'returns an error' do + get path, params: { project_id: project.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to be_empty + end + end + + context 'user with an accessible merge request but no scope' do + where( + params: [ + {}, + { group_id: ' ' }, + { project_id: ' ' }, + { group_id: ' ', project_id: ' ' } + ] + ) + + with_them do + it 'returns an error' do + sign_in(user) + + get path, params: params + + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response).to eq({ 'error' => 'At least one of group_id or project_id must be specified' }) + end + end + end + + context 'user with an accessible merge request by project' do + it 'returns json' do sign_in(user) - get :merge_request_target_branches, params: params + get path, params: { project_id: project.id } - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response).to eq({ 'error' => 'At least one of group_id or project_id must be specified' }) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to contain_exactly(expected_result) + end + end + + context 'user with an accessible merge request by group' do + let(:group) { create(:group) } + let(:user) { create(:user) } + + it 'returns json' do + project.update!(namespace: group) + group.add_owner(user) + + sign_in(user) + + get path, params: { group_id: group.id } + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to contain_exactly(expected_result) end end end - context 'user with an accessible merge request by project' do - it 'returns json' do - sign_in(user) - - get :merge_request_target_branches, params: { project_id: project.id } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to contain_exactly({ 'title' => 'feature' }) - end - end - - context 'user with an accessible merge request by group' do - let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } - let(:user) { create(:user) } - - it 'returns json' do - group.add_owner(user) - - sign_in(user) - - get :merge_request_target_branches, params: { group_id: group.id } - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to contain_exactly({ 'title' => 'feature' }) - end - end + it_behaves_like 'Get merge_request_{}_branches', :merge_request_target_branches, { 'title' => 'test_target_branch' } + it_behaves_like 'Get merge_request_{}_branches', :merge_request_source_branches, { 'title' => 'test_source_branch' } end end diff --git a/spec/features/merge_requests/user_filters_by_source_branch_spec.rb b/spec/features/merge_requests/user_filters_by_source_branch_spec.rb new file mode 100644 index 00000000000..7eade94de2a --- /dev/null +++ b/spec/features/merge_requests/user_filters_by_source_branch_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Merge Requests > User filters by source branch', :js, feature_category: :code_review_workflow do + include FilteredSearchHelpers + + def create_mr(source_branch, target_branch, status) + create(:merge_request, status, source_project: project, + target_branch: target_branch, source_branch: source_branch) + end + + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:user) { project.creator } + + let_it_be(:mr1) { create_mr('source1', 'target1', :opened) } + let_it_be(:mr2) { create_mr('source2', 'target1', :opened) } + let_it_be(:mr3) { create_mr('source1', 'target2', :merged) } + let_it_be(:mr4) { create_mr('source1', 'target2', :closed) } + + before do + sign_in(user) + visit project_merge_requests_path(project) + end + + context 'when filtering by source-branch:source1' do + it 'applies the filter' do + input_filtered_search('source-branch:=source1') + + expect(page).to have_issuable_counts(open: 1, merged: 1, closed: 1, all: 3) + expect(page).to have_content mr1.title + expect(page).not_to have_content mr2.title + end + end + + context 'when filtering by source-branch:source2' do + it 'applies the filter' do + input_filtered_search('source-branch:=source2') + + expect(page).to have_issuable_counts(open: 1, merged: 0, closed: 0, all: 1) + expect(page).not_to have_content mr1.title + expect(page).to have_content mr2.title + end + end + + context 'when filtering by source-branch:non-exists-branch' do + it 'applies the filter' do + input_filtered_search('source-branch:=non-exists-branch') + + expect(page).to have_issuable_counts(open: 0, merged: 0, closed: 0, all: 0) + expect(page).not_to have_content mr1.title + expect(page).not_to have_content mr2.title + end + end + + context 'when filtering by source-branch:!=source1' do + it 'applies the filter' do + input_filtered_search('source-branch:!=source1') + + expect(page).to have_issuable_counts(open: 1, merged: 0, closed: 0, all: 1) + expect(page).not_to have_content mr1.title + expect(page).to have_content mr2.title + end + end +end diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb index 9f94509e3f8..3feb09bea02 100644 --- a/spec/lib/gitlab/redis/multi_store_spec.rb +++ b/spec/lib/gitlab/redis/multi_store_spec.rb @@ -1011,11 +1011,7 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do describe '#close' do subject { multi_store.close } - context 'when using both stores' do - before do - allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(true) - end - + context 'when using both stores are different' do it 'closes both stores' do expect(primary_store).to receive(:close) expect(secondary_store).to receive(:close) @@ -1024,35 +1020,16 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do end end - context 'when using only one store' do + context 'when using identical stores' do before do - allow(multi_store).to receive(:use_primary_and_secondary_stores?).and_return(false) + allow(multi_store).to receive(:same_redis_store?).and_return(true) end - context 'when using primary_store as default store' do - before do - allow(multi_store).to receive(:use_primary_store_as_default?).and_return(true) - end + it 'closes secondary store' do + expect(secondary_store).to receive(:close) + expect(primary_store).not_to receive(:close) - it 'closes primary store' do - expect(primary_store).to receive(:close) - expect(secondary_store).not_to receive(:close) - - subject - end - end - - context 'when using secondary_store as default store' do - before do - allow(multi_store).to receive(:use_primary_store_as_default?).and_return(false) - end - - it 'closes secondary store' do - expect(primary_store).not_to receive(:close) - expect(secondary_store).to receive(:close) - - subject - end + subject end end end diff --git a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb index a27e723e392..9cf9901007c 100644 --- a/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # rubocop: disable RSpec/MultipleMemoizedHelpers -RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do +RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics, feature_category: :shared do shared_examples "a metrics middleware" do context "with mocked prometheus" do include_context 'server metrics with mocked prometheus' @@ -452,11 +452,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do end context 'when emit_sidekiq_histogram_metrics FF is disabled' do - include_context 'server metrics with mocked prometheus' - include_context 'server metrics call' do - let(:stub_subject) { false } - end - subject(:middleware) { described_class.new } let(:job) { {} } @@ -484,16 +479,38 @@ RSpec.describe Gitlab::SidekiqMiddleware::ServerMetrics do stub_feature_flags(emit_sidekiq_histogram_metrics: false) end + # include_context below must run after stubbing FF above because + # the middleware initialization depends on the FF and it's being initialized + # in the 'server metrics call' shared_context + include_context 'server metrics with mocked prometheus' + include_context 'server metrics call' + it 'does not emit histogram metrics' do expect(completion_seconds_metric).not_to receive(:observe) expect(queue_duration_seconds).not_to receive(:observe) expect(failed_total_metric).not_to receive(:increment) + expect(user_execution_seconds_metric).not_to receive(:observe) + expect(db_seconds_metric).not_to receive(:observe) + expect(gitaly_seconds_metric).not_to receive(:observe) + expect(redis_seconds_metric).not_to receive(:observe) + expect(elasticsearch_seconds_metric).not_to receive(:observe) middleware.call(worker, job, queue) { nil } end - it 'emits sidekiq_jobs_completion_seconds_sum metric' do + it 'emits sidekiq_jobs_completion_seconds sum and count metric' do expect(completion_seconds_sum_metric).to receive(:increment).with(labels, monotonic_time_duration) + expect(completion_count_metric).to receive(:increment).with(labels, 1) + + middleware.call(worker, job, queue) { nil } + end + + it 'emits resource usage sum metrics' do + expect(cpu_seconds_sum_metric).to receive(:increment).with(labels, thread_cputime_duration) + expect(db_seconds_sum_metric).to receive(:increment).with(labels, db_duration) + expect(gitaly_seconds_sum_metric).to receive(:increment).with(labels, gitaly_duration) + expect(redis_seconds_sum_metric).to receive(:increment).with(labels, redis_duration) + expect(elasticsearch_seconds_sum_metric).to receive(:increment).with(labels, elasticsearch_duration) middleware.call(worker, job, queue) { nil } end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 04d0226ac22..d3c32da2842 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -725,22 +725,35 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev end end - describe '.recent_target_branches' do + describe '.recent_target_branches and .recent_source_branches' do + def create_mr(source_branch, target_branch, status, remove_source_branch = false) + if remove_source_branch + create(:merge_request, status, :remove_source_branch, source_project: project, + target_branch: target_branch, source_branch: source_branch) + else + create(:merge_request, status, source_project: project, + target_branch: target_branch, source_branch: source_branch) + end + end + let(:project) { create(:project) } - let!(:merge_request1) { create(:merge_request, :opened, source_project: project, target_branch: 'feature') } - let!(:merge_request2) { create(:merge_request, :closed, source_project: project, target_branch: 'merge-test') } - let!(:merge_request3) { create(:merge_request, :opened, source_project: project, target_branch: 'fix') } - let!(:merge_request4) { create(:merge_request, :closed, source_project: project, target_branch: 'feature') } + let!(:merge_request1) { create_mr('source1', 'target1', :opened) } + let!(:merge_request2) { create_mr('source2', 'target2', :closed) } + let!(:merge_request3) { create_mr('source3', 'target3', :opened) } + let!(:merge_request4) { create_mr('source4', 'target1', :closed) } + let!(:merge_request5) { create_mr('source5', 'target4', :merged, true) } before do merge_request1.update_columns(updated_at: 1.day.since) merge_request2.update_columns(updated_at: 2.days.since) merge_request3.update_columns(updated_at: 3.days.since) merge_request4.update_columns(updated_at: 4.days.since) + merge_request5.update_columns(updated_at: 5.days.since) end - it 'returns target branches sort by updated at desc' do - expect(described_class.recent_target_branches).to match_array(%w[feature merge-test fix]) + it 'returns branches sort by updated at desc' do + expect(described_class.recent_target_branches).to match_array(%w[target1 target2 target3 target4]) + expect(described_class.recent_source_branches).to match_array(%w[source1 source2 source3 source4 source5]) end end diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb index 1f841eefff2..578a4821b5e 100644 --- a/spec/requests/api/maven_packages_spec.rb +++ b/spec/requests/api/maven_packages_spec.rb @@ -946,6 +946,22 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do expect(response).to have_gitlab_http_status(:forbidden) end + context 'with basic auth' do + where(:token_type) do + %i[personal_access_token deploy_token job] + end + + with_them do + let(:token) { send(token_type).token } + + it "authorizes upload with #{params[:token_type]} token" do + authorize_upload({}, headers.merge(basic_auth_header(token_type == :job ? ::Gitlab::Auth::CI_JOB_USER : user.username, token))) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + def authorize_upload(params = {}, request_headers = headers) put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers end @@ -1083,6 +1099,22 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do expect(response).to have_gitlab_http_status(:forbidden) end + context 'with basic auth' do + where(:token_type) do + %i[personal_access_token deploy_token job] + end + + with_them do + let(:token) { send(token_type).token } + + it "allows upload with #{params[:token_type]} token" do + upload_file(params: params, request_headers: headers.merge(basic_auth_header(token_type == :job ? ::Gitlab::Auth::CI_JOB_USER : user.username, token))) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + context 'file name is too long' do let(:file_name) { 'a' * (Packages::Maven::FindOrCreatePackageService::MAX_FILE_NAME_LENGTH + 1) } diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb index d9b2b44980c..85ee3ed4183 100644 --- a/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb +++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb @@ -19,6 +19,12 @@ RSpec.shared_context 'server metrics with mocked prometheus' do let(:load_balancing_metric) { double('load balancing metric') } let(:sidekiq_mem_total_bytes) { double('sidekiq mem total bytes') } let(:completion_seconds_sum_metric) { double('sidekiq completion seconds sum metric') } + let(:completion_count_metric) { double('sidekiq completion seconds count metric') } + let(:cpu_seconds_sum_metric) { double('cpu seconds sum metric') } + let(:db_seconds_sum_metric) { double('db seconds sum metric') } + let(:gitaly_seconds_sum_metric) { double('gitaly seconds sum metric') } + let(:redis_seconds_sum_metric) { double('redis seconds sum metric') } + let(:elasticsearch_seconds_sum_metric) { double('elasticsearch seconds sum metric') } before do allow(Gitlab::Metrics).to receive(:histogram).and_call_original @@ -38,6 +44,12 @@ RSpec.shared_context 'server metrics with mocked prometheus' do allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_total, anything).and_return(elasticsearch_requests_total) allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_load_balancing_count, anything).and_return(load_balancing_metric) allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_completion_seconds_sum, anything).and_return(completion_seconds_sum_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_completion_count, anything).and_return(completion_count_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_cpu_seconds_sum, anything).and_return(cpu_seconds_sum_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_db_seconds_sum, anything).and_return(db_seconds_sum_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_gitaly_seconds_sum, anything).and_return(gitaly_seconds_sum_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_redis_requests_duration_seconds_sum, anything).and_return(redis_seconds_sum_metric) + allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_elasticsearch_requests_duration_seconds_sum, anything).and_return(elasticsearch_seconds_sum_metric) allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :all).and_return(running_jobs_metric) allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_concurrency, anything, {}, :all).and_return(concurrency_metric) allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_mem_total_bytes, anything, {}, :all).and_return(sidekiq_mem_total_bytes) @@ -78,12 +90,8 @@ RSpec.shared_context 'server metrics call' do } end - let(:stub_subject) { true } - before do - if stub_subject - allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) - end + allow(subject).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) @@ -101,9 +109,16 @@ RSpec.shared_context 'server metrics call' do allow(redis_requests_total).to receive(:increment) allow(elasticsearch_requests_total).to receive(:increment) allow(completion_seconds_sum_metric).to receive(:increment) + allow(completion_count_metric).to receive(:increment) + allow(cpu_seconds_sum_metric).to receive(:increment) + allow(db_seconds_sum_metric).to receive(:increment) + allow(gitaly_seconds_sum_metric).to receive(:increment) + allow(redis_seconds_sum_metric).to receive(:increment) + allow(elasticsearch_seconds_sum_metric).to receive(:increment) allow(queue_duration_seconds).to receive(:observe) allow(user_execution_seconds_metric).to receive(:observe) allow(db_seconds_metric).to receive(:observe) + allow(db_seconds_sum_metric).to receive(:increment) allow(gitaly_seconds_metric).to receive(:observe) allow(completion_seconds_metric).to receive(:observe) allow(redis_seconds_metric).to receive(:observe) diff --git a/spec/validators/ip_cidr_array_validator_spec.rb b/spec/validators/ip_cidr_array_validator_spec.rb new file mode 100644 index 00000000000..4ea46d714c2 --- /dev/null +++ b/spec/validators/ip_cidr_array_validator_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IpCidrArrayValidator, feature_category: :shared do + let(:model) do + Class.new do + include ActiveModel::Model + include ActiveModel::Validations + + attr_accessor :cidr_array + alias_method :cidr_array_before_type_cast, :cidr_array + + validates :cidr_array, ip_cidr_array: true + end.new + end + + using RSpec::Parameterized::TableSyntax + + # noinspection RubyMismatchedArgumentType - RubyMine is resolving `#|` from Array, instead of Rspec::Parameterized + where(:cidr_array, :validity, :errors) do + # rubocop:disable Layout/LineLength -- The RSpec table syntax often requires long lines for errors + nil | false | { cidr_array: ["must be an array of CIDR values"] } + '' | false | { cidr_array: ["must be an array of CIDR values"] } + ['172.0.0.1/256'] | false | { cidr_array: ["IP '172.0.0.1/256' is not a valid CIDR: Invalid netmask 256"] } + %w[172.0.0.1/24 invalid-CIDR] | false | { cidr_array: ["IP 'invalid-CIDR' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + %w[172.0.0.1/256 invalid-CIDR] | false | { cidr_array: ["IP '172.0.0.1/256' is not a valid CIDR: Invalid netmask 256", "IP 'invalid-CIDR' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + ['172.0.0.1/24', nil] | true | {} + %w[172.0.0.1/24 2001:db8::8:800:200c:417a/128] | true | {} + [] | true | {} + [nil] | true | {} + [''] | true | {} + # rubocop:enable Layout/LineLength + end + + with_them do + before do + model.cidr_array = cidr_array + model.validate + end + + it { expect(model.valid?).to eq(validity) } + it { expect(model.errors.messages).to eq(errors) } + end +end diff --git a/spec/validators/ip_cidr_validator_spec.rb b/spec/validators/ip_cidr_validator_spec.rb new file mode 100644 index 00000000000..213991d9f4f --- /dev/null +++ b/spec/validators/ip_cidr_validator_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe IpCidrValidator, feature_category: :shared do + let(:model) do + Class.new do + include ActiveModel::Model + include ActiveModel::Validations + + attr_accessor :cidr + alias_method :cidr_before_type_cast, :cidr + + validates :cidr, ip_cidr: true + end.new + end + + using RSpec::Parameterized::TableSyntax + + where(:cidr, :validity, :errors) do + # rubocop:disable Layout/LineLength -- The RSpec table syntax often requires long lines for errors' + 'invalid-CIDR' | false | { cidr: ["IP 'invalid-CIDR' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + '172.0.0.1|256' | false | { cidr: ["IP '172.0.0.1|256' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + '172.0.0.1' | false | { cidr: ["IP '172.0.0.1' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + '172.0.0.1/2/12' | false | { cidr: ["IP '172.0.0.1/2/12' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + '172.0.0.1/256' | false | { cidr: ["IP '172.0.0.1/256' is not a valid CIDR: Invalid netmask 256"] } + '2001:db8::8:800:200c:417a/129' | false | { cidr: ["IP '2001:db8::8:800:200c:417a/129' is not a valid CIDR: Prefix must be in range 0..128, got: 129"] } + '2001:db8::8:800:200c:417a' | false | { cidr: ["IP '2001:db8::8:800:200c:417a' is not a valid CIDR: IP should be followed by a slash followed by an integer subnet mask (for example: '192.168.1.0/24')"] } + '2001:db8::8:800:200c:417a/128' | true | {} + '172.0.0.1/32' | true | {} + '' | true | {} + nil | true | {} + # rubocop:enable Layout/LineLength + end + + with_them do + before do + model.cidr = cidr + model.validate + end + + it { expect(model.valid?).to eq(validity) } + it { expect(model.errors.messages).to eq(errors) } + end +end diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb index 4c49f1529f2..f33a4190bf8 100644 --- a/spec/views/groups/edit.html.haml_spec.rb +++ b/spec/views/groups/edit.html.haml_spec.rb @@ -7,7 +7,6 @@ RSpec.describe 'groups/edit.html.haml', feature_category: :groups_and_projects d before do stub_template 'groups/settings/_code_suggestions' => '' - stub_template 'groups/settings/_ai_third_party_settings' => '' end describe '"Share with group lock" setting' do