Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-02-24 18:09:28 +00:00
parent 668334016b
commit 181c045a67
51 changed files with 414 additions and 114 deletions

View File

@ -540,7 +540,6 @@ Layout/LineLength:
- 'ee/app/helpers/ee/mirror_helper.rb'
- 'ee/app/helpers/ee/notes_helper.rb'
- 'ee/app/helpers/ee/profiles_helper.rb'
- 'ee/app/helpers/ee/projects_helper.rb'
- 'ee/app/helpers/ee/subscribable_banner_helper.rb'
- 'ee/app/helpers/epics_helper.rb'
- 'ee/app/helpers/gitlab_subscriptions/upcoming_reconciliation_helper.rb'
@ -1163,7 +1162,6 @@ Layout/LineLength:
- 'ee/spec/helpers/projects/on_demand_scans_helper_spec.rb'
- 'ee/spec/helpers/projects/project_members_helper_spec.rb'
- 'ee/spec/helpers/projects/security/dast_profiles_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/push_rules_helper_spec.rb'
- 'ee/spec/helpers/subscriptions_helper_spec.rb'
- 'ee/spec/helpers/timeboxes_helper_spec.rb'
@ -1960,7 +1958,6 @@ Layout/LineLength:
- 'lib/api/project_snippets.rb'
- 'lib/api/project_templates.rb'
- 'lib/api/projects.rb'
- 'lib/api/projects_relation_builder.rb'
- 'lib/api/pypi_packages.rb'
- 'lib/api/releases.rb'
- 'lib/api/repositories.rb'

View File

@ -259,7 +259,6 @@ Lint/UnusedMethodArgument:
- 'lib/api/helpers.rb'
- 'lib/api/helpers/notes_helpers.rb'
- 'lib/api/merge_requests.rb'
- 'lib/api/projects_relation_builder.rb'
- 'lib/atlassian/jira_connect/client.rb'
- 'lib/banzai/filter/playable_link_filter.rb'
- 'lib/banzai/filter/references/abstract_reference_filter.rb'

View File

@ -252,7 +252,6 @@ RSpec/BeforeAllRoleAssignment:
- 'ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb'
- 'ee/spec/helpers/projects/on_demand_scans_helper_spec.rb'
- 'ee/spec/helpers/projects/project_members_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/subscriptions_helper_spec.rb'
- 'ee/spec/helpers/timeboxes_helper_spec.rb'
- 'ee/spec/helpers/tree_helper_spec.rb'

View File

@ -251,7 +251,6 @@ RSpec/ContextWording:
- 'ee/spec/helpers/ee/personal_access_tokens_helper_spec.rb'
- 'ee/spec/helpers/ee/projects/security/api_fuzzing_configuration_helper_spec.rb'
- 'ee/spec/helpers/license_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/roadmaps_helper_spec.rb'
- 'ee/spec/helpers/security_helper_spec.rb'
- 'ee/spec/helpers/subscriptions_helper_spec.rb'

View File

@ -41,7 +41,6 @@ RSpec/ExampleWithoutDescription:
- 'ee/spec/helpers/ee/projects/security/dast_configuration_helper_spec.rb'
- 'ee/spec/helpers/ee/projects/security/sast_configuration_helper_spec.rb'
- 'ee/spec/helpers/ee/users/callouts_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/users_helper_spec.rb'
- 'ee/spec/lib/api/entities/clusters/receptive_agent_spec.rb'
- 'ee/spec/lib/ee/api/entities/experiment_spec.rb'

View File

@ -194,7 +194,6 @@ RSpec/NamedSubject:
- 'ee/spec/helpers/kerberos_helper_spec.rb'
- 'ee/spec/helpers/license_helper_spec.rb'
- 'ee/spec/helpers/projects/security/dast_profiles_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/secrets_helper_spec.rb'
- 'ee/spec/helpers/users_helper_spec.rb'
- 'ee/spec/helpers/vulnerabilities_helper_spec.rb'

View File

@ -36,7 +36,6 @@ RSpec/ReceiveMessages:
- 'ee/spec/helpers/license_monitoring_helper_spec.rb'
- 'ee/spec/helpers/nav/new_dropdown_helper_spec.rb'
- 'ee/spec/helpers/projects/project_members_helper_spec.rb'
- 'ee/spec/helpers/projects_helper_spec.rb'
- 'ee/spec/helpers/routing/pseudonymization_helper_spec.rb'
- 'ee/spec/helpers/sidebars_helper_spec.rb'
- 'ee/spec/helpers/subscriptions_helper_spec.rb'

View File

@ -110,7 +110,6 @@ Style/FormatString:
- 'ee/app/helpers/ee/groups_helper.rb'
- 'ee/app/helpers/ee/import_helper.rb'
- 'ee/app/helpers/ee/profiles_helper.rb'
- 'ee/app/helpers/ee/projects_helper.rb'
- 'ee/app/helpers/ee/timeboxes_helper.rb'
- 'ee/app/helpers/vulnerabilities_helper.rb'
- 'ee/app/mailers/ee/emails/admin_notification.rb'

View File

@ -220,7 +220,6 @@ Style/GuardClause:
- 'ee/app/helpers/ee/auth_helper.rb'
- 'ee/app/helpers/ee/nav/new_dropdown_helper.rb'
- 'ee/app/helpers/ee/projects/pipeline_helper.rb'
- 'ee/app/helpers/ee/projects_helper.rb'
- 'ee/app/models/allowed_email_domain.rb'
- 'ee/app/models/app_sec/fuzzing/coverage/corpus.rb'
- 'ee/app/models/approval_merge_request_rule_source.rb'

View File

@ -235,7 +235,6 @@ Style/IfUnlessModifier:
- 'ee/app/helpers/ee/merge_requests_helper.rb'
- 'ee/app/helpers/ee/notes_helper.rb'
- 'ee/app/helpers/ee/projects/pipeline_helper.rb'
- 'ee/app/helpers/ee/projects_helper.rb'
- 'ee/app/models/allowed_email_domain.rb'
- 'ee/app/models/app_sec/fuzzing/coverage/corpus.rb'
- 'ee/app/models/concerns/elastic/application_versioned_search.rb'
@ -443,7 +442,6 @@ Style/IfUnlessModifier:
- 'lib/api/pages_domains.rb'
- 'lib/api/project_snippets.rb'
- 'lib/api/projects.rb'
- 'lib/api/projects_relation_builder.rb'
- 'lib/api/protected_branches.rb'
- 'lib/api/repositories.rb'
- 'lib/api/rubygem_packages.rb'

View File

@ -11,7 +11,6 @@ Style/RedundantParentheses:
- 'config/initializers/8_devise.rb'
- 'config/initializers/zz_metrics.rb'
- 'ee/app/controllers/groups/billings_controller.rb'
- 'ee/app/helpers/ee/projects_helper.rb'
- 'ee/app/models/push_rule.rb'
- 'ee/app/services/concerns/approval_rules/updater.rb'
- 'ee/app/services/merge_trains/refresh_service.rb'

View File

@ -31,14 +31,15 @@ export default {
</script>
<template>
<div class="gl-flex gl-grow gl-items-center gl-justify-end">
<li role="presentation" class="gl-flex gl-grow gl-items-center gl-justify-end">
<label id="database-selector-label" class="gl-sr-only">{{ __('Selected database') }}</label>
<gl-collapsible-listbox
v-model="selected"
:items="databases"
placement="bottom-end"
:toggle-text="selectedDatabase"
toggle-aria-labelled-by="label"
toggle-aria-labelled-by="database-selector-label"
@select="selectDatabase"
/>
</div>
</li>
</template>

View File

@ -41,11 +41,12 @@ export default {
</script>
<template>
<gl-form-group :label="$options.i18n.label">
<gl-form-group id="organization-role-field" :label="$options.i18n.label">
<gl-collapsible-listbox
v-model="accessLevel"
block
toggle-class="gl-form-input-xl"
toggle-aria-labelled-by="organization-role-field"
:items="$options.roleListboxItems"
/>
<input :id="$options.inputId" :name="inputName" :value="accessLevel" type="hidden" />

View File

@ -84,8 +84,8 @@ export default {
items.push({
text: this.$options.i18n.delete,
action: this.onDelete,
variant: 'danger',
extraAttrs: {
class: '!gl-text-red-500',
'data-testid': `delete-label-action`,
},
});

View File

@ -138,9 +138,7 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
props: {
refType,
currentRef: ref,
// BlobControls:
projectPath,
// RefSelector:
projectId,
},
});

View File

@ -668,7 +668,7 @@ export default {
optimisticResponse: designUploadOptimisticResponse(this.filesToBeSaved),
variables: {
files: this.filesToBeSaved,
projectPath: this.fullPath,
projectPath: this.workItemFullPath,
iid: this.iid,
},
context: {
@ -682,7 +682,7 @@ export default {
return this.$apollo
.mutate(mutationPayload)
.then((res) => this.onUploadDesignDone(res))
.catch(() => this.onUploadDesignError());
.catch((error) => this.onUploadDesignError(error));
},
afterUploadDesign(store, { data: { designManagementUpload } }) {
updateStoreAfterUploadDesign(store, designManagementUpload, this.designCollectionQueryBody);
@ -702,7 +702,8 @@ export default {
// reset state
this.resetFilesToBeSaved();
},
onUploadDesignError() {
onUploadDesignError(error) {
Sentry.captureException(error);
this.resetFilesToBeSaved();
this.designUploadError = UPLOAD_DESIGN_ERROR_MESSAGE;
},

View File

@ -275,7 +275,7 @@ export default {
<template>
<li class="tree-item !gl-px-0 !gl-py-2">
<div class="gl-flex gl-items-start">
<div v-if="hasIndirectChildren" class="gl-mr-2 gl-h-7 gl-w-5">
<div v-if="hasIndirectChildren" class="gl-mr-4 gl-h-7 gl-w-5">
<gl-button
v-if="shouldExpandChildren"
v-gl-tooltip.hover
@ -285,7 +285,7 @@ export default {
category="tertiary"
size="small"
:loading="isLoadingChildren && !fetchNextPageInProgress"
class="!gl-px-0 !gl-py-3"
class="!gl-py-3"
data-testid="expand-child"
@click.stop="toggleItem"
/>

View File

@ -344,3 +344,12 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem;
overflow-y: scroll !important;
}
}
.design-list-item {
height: 160px;
text-decoration: none;
&:hover {
border-color: var(--gray-400, $gray-400);
}
}

View File

@ -3,6 +3,8 @@
class Admin::HealthCheckController < Admin::ApplicationController
feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
authorize! :read_admin_health_check, only: [:show]
def show
@errors = HealthCheck::Utils.process_checks(checks)
end

View File

@ -37,7 +37,8 @@ module MergeRequests
committers: [merge_request_diff: [:merge_request_diff_commits]],
suggested_reviewers: [:predictions],
diff_stats: [latest_merge_request_diff: [:merge_request_diff_commits]],
source_branch_exists: [:source_project, { source_project: [:route] }]
source_branch_exists: [:source_project, { source_project: [:route] }],
squash_read_only: { target_project: :project_setting }
}
end
end

View File

@ -266,6 +266,10 @@ module Types
HEREDOC
field :squash_on_merge, GraphQL::Types::Boolean, null: false, method: :squash_on_merge?,
description: 'Indicates if the merge request will be squashed when merged.'
field :squash_read_only, GraphQL::Types::Boolean,
null: false,
description: 'Indicates if `squashReadOnly` is enabled.',
method: :squash_readonly?
field :timelogs, Types::TimelogType.connection_type, null: false,
description: 'Timelogs on the merge request.'

View File

@ -254,6 +254,17 @@ class WorkItem < Issue
end
end
def lazy_user_notes
BatchLoader.for(id).batch(default_value: []) do |work_item_ids, loader|
issue_user_notes = ::Note.where(noteable_type: 'Issue', noteable_id: work_item_ids)
issue_user_notes.each_batch do |batch|
batch.user.each do |note|
loader.call(note.noteable_id) { |notes| notes << note }
end
end
end
end
private
override :parent_link_confidentiality

View File

@ -5,25 +5,25 @@
description: page_description,
options: { data: { event_tracking_load: 'true', event_tracking: 'view_admin_health_check_pageload' } } )
.form-group
= label_tag :health_check_access_token, s_('HealthCheck|Access token')
.gl-flex.gl-gap-3
= text_field_tag :health_check_access_token, Gitlab::CurrentSettings.health_check_access_token, class: "form-control gl-w-28", readonly: true, data: { testid: 'health_check_token' }
= render Pajamas::ButtonComponent.new(href: reset_health_check_token_admin_application_settings_path, method: :put, button_options: { data: { confirm: _('Are you sure you want to reset the health check token?') } }) do
= _("Reset token")
- if can?(current_user, :admin_all_resources)
.form-group
= label_tag :health_check_access_token, s_('HealthCheck|Access token')
.gl-flex.gl-gap-3
= text_field_tag :health_check_access_token, Gitlab::CurrentSettings.health_check_access_token, class: "form-control gl-w-28", readonly: true, data: { testid: 'health_check_token' }
= render Pajamas::ButtonComponent.new(href: reset_health_check_token_admin_application_settings_path, method: :put, button_options: { data: { confirm: _('Are you sure you want to reset the health check token?') } }) do
= _("Reset token")
- help_url = help_page_path('administration/monitoring/health_check.md')
- help_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_url }
%p.gl-mb-1= html_escape(s_('HealthCheck|Health information can be retrieved from the following endpoints. More information is available in the %{linkStart}health check documentation%{linkEnd}.')) % { linkStart: help_start, linkEnd: '</a>'.html_safe }
%ul.gl-mb-6
%li.gl-mb-1
%code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li.gl-mb-1
%code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li.gl-mb-1
%code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
= render_if_exists 'admin/health_check/health_check_url'
- help_url = help_page_path('administration/monitoring/health_check.md')
- help_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_url }
%p.gl-mb-1= html_escape(s_('HealthCheck|Health information can be retrieved from the following endpoints. More information is available in the %{linkStart}health check documentation%{linkEnd}.')) % { linkStart: help_start, linkEnd: '</a>'.html_safe }
%ul.gl-mb-6
%li.gl-mb-1
%code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li.gl-mb-1
%code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token)
%li.gl-mb-1
%code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token)
= render_if_exists 'admin/health_check/health_check_url'
- status_icon = no_errors ? 'check' : 'warning-solid'
- status_text = no_errors ? s_('HealthCheck|Healthy') : s_('HealthCheck|Unhealthy')

View File

@ -10,7 +10,7 @@
= link_to user_path(user), class: 'gl-text-default', data: { event_tracking: 'click_search_result', event_label: @scope, event_value: position } do
.gl-inline-block.gl-font-bold= simple_search_highlight_and_truncate(user.name, @search_term)
= user_status(user)
%div{ class: '!gl-text-left' }= simple_search_highlight_and_truncate(user.to_reference, @search_term)
%div{ class: '!gl-text-left gl-text-subtle' }= simple_search_highlight_and_truncate(user.to_reference, @search_term)
%td.gl-text-right{ data: { label: _('Activity') } }
%div
%span.gl-font-bold= _('User created:')

View File

@ -0,0 +1,13 @@
---
table_name: ai_active_context_migrations
classes:
- Ai::ActiveContext::Migration
feature_categories:
- global_search
description: Tracks the state and progress of AI active context migrations.
Each migration is identified by a unique version timestamp and is used for computing or updating collections in ai context abstraction layer.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181238
milestone: '17.10'
gitlab_schema: gitlab_main_cell
exempt_from_sharding: true
table_size: small

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
class AddActiveContextMigrations < Gitlab::Database::Migration[2.2]
milestone '17.10'
FAILED_STATE_ENUM = 255
INDEX_NAME = 'index_ai_active_context_migrations_on_connection_and_status'
UNIQUE_INDEX_NAME = 'index_ai_active_context_migrations_on_connection_and_version'
CONSTRAINT_NAME = 'c_ai_active_context_migrations_on_retries_left'
VERSION_CONSTRAINT_NAME = 'c_ai_active_context_migrations_version_format'
CONSTRAINT_QUERY = <<~SQL
(retries_left > 0) OR (retries_left = 0 AND status = #{FAILED_STATE_ENUM})
SQL
def change
create_table :ai_active_context_migrations do |t|
# Fixed size columns (8 bytes)
t.references :connection, index: false,
foreign_key: { to_table: :ai_active_context_connections, on_delete: :cascade }, null: false
t.datetime_with_timezone :started_at
t.datetime_with_timezone :completed_at
t.timestamps_with_timezone
# Fixed size columns (4 bytes)
t.integer :status, limit: 2, null: false, default: 0
t.integer :retries_left, limit: 2, null: false
# Variable size columns
t.text :version, null: false, limit: 255
t.jsonb :metadata, null: false, default: {}
t.text :error_message, limit: 1024
t.index [:connection_id, :status], name: INDEX_NAME
t.index [:connection_id, :version], unique: true, name: UNIQUE_INDEX_NAME
# Check constraint ensures retries_left is only non-zero for non-failed migrations
t.check_constraint CONSTRAINT_QUERY, name: CONSTRAINT_NAME
# Check constraint ensures version is a 14-digit timestamp format (YYYYMMDDHHMMSS)
t.check_constraint "version ~ '^[0-9]{14}$'", name: VERSION_CONSTRAINT_NAME
end
end
end

View File

@ -0,0 +1 @@
ff25d3c1a1f25af9447a6463ff867ac4d29741f440a5b2bc91d757cbb9aa77f6

View File

@ -7157,6 +7157,33 @@ CREATE SEQUENCE ai_active_context_connections_id_seq
ALTER SEQUENCE ai_active_context_connections_id_seq OWNED BY ai_active_context_connections.id;
CREATE TABLE ai_active_context_migrations (
id bigint NOT NULL,
connection_id bigint NOT NULL,
started_at timestamp with time zone,
completed_at timestamp with time zone,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
status smallint DEFAULT 0 NOT NULL,
retries_left smallint NOT NULL,
version text NOT NULL,
metadata jsonb DEFAULT '{}'::jsonb NOT NULL,
error_message text,
CONSTRAINT c_ai_active_context_migrations_on_retries_left CHECK (((retries_left > 0) OR ((retries_left = 0) AND (status = 255)))),
CONSTRAINT c_ai_active_context_migrations_version_format CHECK ((version ~ '^[0-9]{14}$'::text)),
CONSTRAINT check_184ab3430e CHECK ((char_length(error_message) <= 1024)),
CONSTRAINT check_b2e8a34818 CHECK ((char_length(version) <= 255))
);
CREATE SEQUENCE ai_active_context_migrations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ai_active_context_migrations_id_seq OWNED BY ai_active_context_migrations.id;
CREATE TABLE ai_agent_version_attachments (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@ -24978,6 +25005,8 @@ ALTER TABLE ONLY ai_active_context_collections ALTER COLUMN id SET DEFAULT nextv
ALTER TABLE ONLY ai_active_context_connections ALTER COLUMN id SET DEFAULT nextval('ai_active_context_connections_id_seq'::regclass);
ALTER TABLE ONLY ai_active_context_migrations ALTER COLUMN id SET DEFAULT nextval('ai_active_context_migrations_id_seq'::regclass);
ALTER TABLE ONLY ai_agent_version_attachments ALTER COLUMN id SET DEFAULT nextval('ai_agent_version_attachments_id_seq'::regclass);
ALTER TABLE ONLY ai_agent_versions ALTER COLUMN id SET DEFAULT nextval('ai_agent_versions_id_seq'::regclass);
@ -26919,6 +26948,9 @@ ALTER TABLE ONLY ai_active_context_collections
ALTER TABLE ONLY ai_active_context_connections
ADD CONSTRAINT ai_active_context_connections_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ai_active_context_migrations
ADD CONSTRAINT ai_active_context_migrations_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ai_agent_version_attachments
ADD CONSTRAINT ai_agent_version_attachments_pkey PRIMARY KEY (id);
@ -31465,6 +31497,10 @@ CREATE INDEX index_agent_user_access_on_project_id ON agent_user_access_project_
CREATE UNIQUE INDEX index_ai_active_context_connections_on_name ON ai_active_context_connections USING btree (name);
CREATE INDEX index_ai_active_context_migrations_on_connection_and_status ON ai_active_context_migrations USING btree (connection_id, status);
CREATE UNIQUE INDEX index_ai_active_context_migrations_on_connection_and_version ON ai_active_context_migrations USING btree (connection_id, version);
CREATE INDEX index_ai_agent_version_attachments_on_ai_agent_version_id ON ai_agent_version_attachments USING btree (ai_agent_version_id);
CREATE INDEX index_ai_agent_version_attachments_on_ai_vectorizable_file_id ON ai_agent_version_attachments USING btree (ai_vectorizable_file_id);
@ -41320,6 +41356,9 @@ ALTER TABLE ONLY ml_candidate_metadata
ALTER TABLE ONLY merge_request_merge_schedules
ADD CONSTRAINT fk_rails_5294434bc3 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
ALTER TABLE ONLY ai_active_context_migrations
ADD CONSTRAINT fk_rails_52b6529477 FOREIGN KEY (connection_id) REFERENCES ai_active_context_connections(id) ON DELETE CASCADE;
ALTER TABLE ONLY elastic_group_index_statuses
ADD CONSTRAINT fk_rails_52b9969b12 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;

View File

@ -29305,6 +29305,7 @@ Defines which user roles, users, or groups can merge into a protected branch.
| <a id="mergerequestsourceprojectid"></a>`sourceProjectId` | [`Int`](#int) | ID of the merge request source project. |
| <a id="mergerequestsquash"></a>`squash` | [`Boolean!`](#boolean) | Indicates if the merge request is set to be squashed when merged. [Project settings](https://docs.gitlab.com/ee/user/project/merge_requests/squash_and_merge.html#configure-squash-options-for-a-project) may override this value. Use `squash_on_merge` instead to take project squash options into account. |
| <a id="mergerequestsquashonmerge"></a>`squashOnMerge` | [`Boolean!`](#boolean) | Indicates if the merge request will be squashed when merged. |
| <a id="mergerequestsquashreadonly"></a>`squashReadOnly` | [`Boolean!`](#boolean) | Indicates if `squashReadOnly` is enabled. |
| <a id="mergerequeststate"></a>`state` | [`MergeRequestState!`](#mergerequeststate) | State of the merge request. |
| <a id="mergerequestsubscribed"></a>`subscribed` | [`Boolean!`](#boolean) | Indicates if the currently logged in user is subscribed to this merge request. |
| <a id="mergerequestsuggestedreviewers"></a>`suggestedReviewers` | [`SuggestedReviewersType`](#suggestedreviewerstype) | Suggested reviewers for merge request. |

View File

@ -51,7 +51,7 @@ GET /projects/:id/resource_groups/:key
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `key` | string | yes | The key of the resource group |
| `key` | string | yes | The URL-encoded key of the resource group. For example, use `resource%5Fa` instead of `resource_a`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/resource_groups/production"
@ -78,7 +78,7 @@ GET /projects/:id/resource_groups/:key/upcoming_jobs
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `key` | string | yes | The key of the resource group |
| `key` | string | yes | The URL-encoded key of the resource group. For example, use `resource%5Fa` instead of `resource_a`. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/50/resource_groups/production/upcoming_jobs"
@ -177,7 +177,7 @@ PUT /projects/:id/resource_groups/:key
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths) |
| `key` | string | yes | The key of the resource group |
| `key` | string | yes |The URL-encoded key of the resource group. For example, use `resource%5Fa` instead of `resource_a`. |
| `process_mode` | string | no | The process mode of the resource group. One of `unordered`, `oldest_first` or `newest_first`. Read [process modes](../ci/resource_groups/_index.md#process-modes) for more information. |
```shell

View File

@ -122,23 +122,25 @@ delete_resources: on_stop # Resources are removed when environment is stopped
Environment templates support limited variable substitution.
The following variables are available:
| Category | Variable | Description |
|----------|----------|-------------|
| Agent | `{{ .agent.id }}` | The agent identifier. |
| Agent | `{{ .agent.name }}` | The agent name. |
| Agent | `{{ .agent.url }}` | The agent URL. |
| Environment | `{{ .environment.name }}` | The environment name. |
| Environment | `{{ .environment.slug }}` | The environment slug. |
| Environment | `{{ .environment.url }}` | The environment URL. |
| Environment | `{{ .environment.tier }}` | The environment tier. |
| Project | `{{ .project.id }}` | The project identifier. |
| Project | `{{ .project.slug }}` | The project slug. |
| Project | `{{ .project.path }}` | The project path. |
| Project | `{{ .project.url }}` | The project URL. |
| CI Pipeline | `{{ .ci_pipeline.id }}` | The pipeline identifier. |
| CI Job | `{{ .ci_job.id }}` | The CI/CD job identifier. |
| User | `{{ .user.id }}` | The user identifier. |
| User | `{{ .user.username }}` | The username. |
| Category | Variable | Description | Type | Default value when not set |
|----------------|-------------------------------|---------------------------|---------|----------------------------|
| Agent | `{{ .agent.id }}` | The agent ID. | Integer | N/A |
| Agent | `{{ .agent.name }}` | The agent name. | String | N/A |
| Agent | `{{ .agent.url }}` | The agent URL. | String | N/A |
| Environment | `{{ .environment.id }}` | The environment ID. | Integer | N/A |
| Environment | `{{ .environment.name }}` | The environment name. | String | N/A |
| Environment | `{{ .environment.slug }}` | The environment slug. | String | N/A |
| Environment | `{{ .environment.url }}` | The environment URL. | String | Empty string |
| Environment | `{{ .environment.page_url }}` | The environment page URL. | String | N/A |
| Environment | `{{ .environment.tier }}` | The environment tier. | String | N/A |
| Project | `{{ .project.id }}` | The project ID. | Integer | N/A |
| Project | `{{ .project.slug }}` | The project slug. | String | N/A |
| Project | `{{ .project.path }}` | The project path. | String | N/A |
| Project | `{{ .project.url }}` | The project URL. | String | N/A |
| CI/CD Pipeline | `{{ .ci_pipeline.id }}` | The pipeline ID. | Integer | Zero |
| CI/CD Job | `{{ .ci_job.id }}` | The CI/CD job ID. | Integer | Zero |
| User | `{{ .user.id }}` | The user ID. | Integer | N/A |
| User | `{{ .user.username }}` | The username. | String | N/A |
All variables should be referenced using the double curly brace syntax, for example: `{{ .project.id }}`.
See [`text/template`](https://pkg.go.dev/text/template) documentation for more information on the templating system used.
@ -151,26 +153,28 @@ The following labels are defined on every resource created by GitLab. The values
- `agent.gitlab.com/id-<agent_id>: ""`
- `agent.gitlab.com/project_id-<project_id>: ""`
- `agent.gitlab.com/env-<kubernetes_namespace>: ""`
- `agent.gitlab.com/env-<gitlab_environment_slug>-<project_id>-<agent_id>: ""`
- `agent.gitlab.com/environment_slug-<gitlab_environment_slug>: ""`
On every resource created by GitLab, an `agent.gitlab.com/env-<kubernetes_namespace>` annotation is defined. The value of the annotation is a JSON object with the following keys:
On every resource created by GitLab, an `agent.gitlab.com/env-<gitlab_environment_slug>-<project_id>-<agent_id>` annotation is defined.
The value of the annotation is a JSON object with the following keys:
| Key | Description |
|-----|-------------|
| `environment_id` | The GitLab environment ID. |
| `environment_name` | The GitLab environment name. |
| `environment_slug` | The GitLab environment slug. |
| `environment_page_url` | The link to the GitLab environment page. |
| `environment_tier` | The GitLab environment deployment tier. |
| `agent_id` | The agent ID. |
| `agent_name` | The agent name. |
| Key | Description |
|-----|--------------------------------------------------|
| `environment_id` | The GitLab environment ID. |
| `environment_name` | The GitLab environment name. |
| `environment_slug` | The GitLab environment slug. |
| `environment_url` | The link to the environment. Optional. |
| `environment_page_url` | The link to the GitLab environment page. |
| `environment_tier` | The GitLab environment deployment tier. |
| `agent_id` | The agent ID. |
| `agent_name` | The agent name. |
| `agent_url` | The agent URL in the agent registration project. |
| `project_id` | The GitLab project ID. |
| `project_slug` | The GitLab project slug. |
| `project_path` | The full GitLab project path. |
| `project_url` | The link to the GitLab project. |
| `template_name` | The name of the template used. |
| `project_id` | The GitLab project ID. |
| `project_slug` | The GitLab project slug. |
| `project_path` | The full GitLab project path. |
| `project_url` | The link to the GitLab project. |
| `template_name` | The name of the template used. |
## Troubleshooting

View File

@ -239,17 +239,17 @@ The [maximum file size](../../administration/instance_limits.md#file-size-limits
for a package uploaded to the [GitLab package registry](../packages/package_registry/_index.md)
varies by format:
| Package type | GitLab.com |
|---------------------------|------------|
| Conan | 5 GB |
| Generic | 5 GB |
| Helm | 5 MB |
| Maven | 5 GB |
| npm | 5 GB |
| NuGet | 5 GB |
| PyPI | 5 GB |
| Terraform | 1 GB |
| Machine learning model | 10 GB |
| Package type | GitLab.com |
|------------------------|------------------------------------|
| Conan | 5 GB |
| Generic | 5 GB |
| Helm | 5 MB |
| Maven | 5 GB |
| npm | 5 GB |
| NuGet | 5 GB |
| PyPI | 5 GB |
| Terraform | 1 GB |
| Machine learning model | 10 GB (uploads are capped at 5 GB) |
## Account and limit settings

View File

@ -12,7 +12,7 @@ title: Value stream analytics
{{< /details >}}
Value stream analytics measures the time it takes to go from an idea to production.
Value stream analytics measures the time it takes to go from an idea to production by tracking merge request or issue events.
A **value stream** is the entire work process that delivers value to customers. For example,
the [DevOps lifecycle](https://about.gitlab.com/stages-devops-lifecycle/) is a value stream that starts
@ -30,7 +30,7 @@ Value stream analytics helps businesses:
- Visualize their end-to-end DevSecOps workstreams.
- Identify and solve inefficiencies.
- Optimize their workstreams to deliver more value, faster.
- Optimize their workstreams to deliver more value, faster (for example, [reducing merge request review time](https://about.gitlab.com/blog/2025/02/20/how-we-reduced-mr-review-time-with-value-stream-management/)).
Value stream analytics is available for projects and groups.
@ -119,6 +119,8 @@ These events play a key role in the duration calculation, which is calculated by
To learn what start and end events can be paired, see [Validating start and end events](../../../development/value_stream_analytics.md#validating-start-and-end-events).
You can share your ideas or feedback about stage events in [issue 520962](https://gitlab.com/gitlab-org/gitlab/-/issues/520962).
### How value stream analytics aggregates data
{{< details >}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -100,7 +100,7 @@ first in the list of changed files. To copy a merge request link that shows your
1. Below the merge request title, select **Changes**.
1. Find the file you want to show first. Right-click the name of the file to copy the link to it.
1. When you visit that link, your chosen file is shown at the top of the list. The file browser
shows a link icon ({{< icon name="link" >}}) next to the file name:
shows a link icon ({{< icon name="link" >}}) next to the filename:
![A merge request showing a YAML file at the top of the list.](img/linked_file_v17_4.png)
@ -230,13 +230,13 @@ To change how a merge request shows changed lines:
{{< tab title="Inline changes" >}}
![inline changes](img/changes-inline_v14_8.png)
![inline changes](img/changes-inline_v17_10.png)
{{< /tab >}}
{{< tab title="Side-by-side changes" >}}
![side-by-side changes](img/changes-sidebyside_v14_8.png)
![side-by-side changes](img/changes-sidebyside_v17_10.png)
{{< /tab >}}
@ -303,7 +303,7 @@ When reviewing code changes, you can hide inline comments:
1. Select **Code > Merge requests** and find your merge request.
1. Below the title, select **Changes**.
1. Scroll to the file that contains the comments you want to hide.
1. Scroll to the line the comment is attached to, and select **Collapse** ({{< icon name="collapse" >}}):
1. Scroll to the line the comment is attached to. In the gutter margin, select **Collapse** ({{< icon name="collapse" >}}):
![collapse a comment](img/collapse-comment_v17_1.png)
To expand inline comments and show them again:
@ -312,8 +312,8 @@ To expand inline comments and show them again:
1. Select **Code > Merge requests** and find your merge request.
1. Below the title, select **Changes**.
1. Scroll to the file that contains the collapsed comments you want to show.
1. Scroll to the line the comment is attached to, and select the user avatar:
![expand a comment](img/expand-comment_v14_8.png)
1. Scroll to the line the comment is attached to. In the gutter margin, select the user avatar:
![expand a comment](img/expand-comment_v17_10.png)
## Ignore whitespace changes
@ -326,12 +326,12 @@ a merge request. You can choose to hide or show whitespace changes:
1. Before the list of changed files, select **Preferences** ({{< icon name="preferences" >}}).
1. Select or clear **Show whitespace changes**:
![MR diff](img/merge_request_diff_v14_2.png)
![A merge request diff with the Preferences menu expanded](img/merge_request_diff_v17_10.png)
## Mark files as viewed
When reviewing a merge request with many files multiple times, you can ignore files
you've already reviewed. To hide files that haven't changed since your last review:
you've already reviewed. To hide files that haven't changed after your last review:
1. On the left sidebar, select **Search or go to** and find your project.
1. Select **Code > Merge requests** and find your merge request.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -142,9 +142,7 @@ In that case, if _any_ of these protected tags have a setting like
**Allowed to create**, then `production-stable` also inherit this setting.
If you select a protected tag's name, GitLab displays a list of
all matching tags:
![Protected tag matches](img/protected_tag_matches_v9_1.png)
all matching tags.
## Prevent tag creation with the same name as branches

View File

@ -14,7 +14,9 @@ module API
preload_repository_cache(projects_relation)
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user]
if options[:current_user]
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute
end
preload_member_roles(projects_relation, options[:current_user]) if options[:current_user]
preload_groups(projects_relation) if options[:with] == Entities::Project
@ -24,7 +26,7 @@ module API
# This is overridden by the specific Entity class to
# preload assocations that it needs
def preload_relation(projects_relation, options = {})
def preload_relation(projects_relation, _options = {})
projects_relation
end

View File

@ -53837,6 +53837,9 @@ msgstr ""
msgid "Selected commits"
msgstr ""
msgid "Selected database"
msgstr ""
msgid "Selected for all items."
msgstr ""

View File

@ -1,32 +1,190 @@
import * as utils from '~/blob/utils';
import { TEST_HOST } from 'helpers/test_constants';
import setWindowLocation from 'helpers/set_window_location_helper';
describe('Blob utilities', () => {
beforeEach(() => {
jest.clearAllMocks();
document.body.innerHTML = '';
});
describe('getPageParamValue', () => {
it('returns empty string if no perPage parameter is provided', () => {
const pageParamValue = utils.getPageParamValue(5);
expect(pageParamValue).toEqual('');
});
it('returns empty string if page is equal 1', () => {
const pageParamValue = utils.getPageParamValue(1000, 1000);
expect(pageParamValue).toEqual('');
});
it('returns correct page parameter value', () => {
const pageParamValue = utils.getPageParamValue(1001, 1000);
expect(pageParamValue).toEqual(2);
});
it('accepts strings as a parameter and returns correct result', () => {
const pageParamValue = utils.getPageParamValue('1001', '1000');
expect(pageParamValue).toEqual(2);
});
});
describe('getPageSearchString', () => {
it('returns empty search string if page parameter is empty value', () => {
const path = utils.getPageSearchString('/blamePath', '');
expect(path).toEqual('');
});
it('returns correct search string if value is provided', () => {
const searchString = utils.getPageSearchString('/blamePath', 3);
const searchString = utils.getPageSearchString('http://project/blamePath', 3);
expect(searchString).toEqual('?page=3');
});
});
describe('moveToFilePermalink', () => {
const initialTitle = 'Title · Test';
let windowHistorySpy;
beforeEach(() => {
windowHistorySpy = jest.spyOn(window.history, 'pushState');
setWindowLocation(TEST_HOST);
document.title = initialTitle;
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should do nothing when permalink element does not exist', () => {
utils.moveToFilePermalink();
expect(windowHistorySpy).not.toHaveBeenCalled();
expect(document.title).toMatch(initialTitle);
});
it('should do nothing when permalink element exists but has no href', () => {
document.body.innerHTML = `
<div class="js-data-file-blob-permalink-url">
<a data-testid="permalink"></a>
</div>
`;
utils.moveToFilePermalink();
expect(windowHistorySpy).not.toHaveBeenCalled();
expect(document.title).toMatch(initialTitle);
});
it('should not update history when URL is not different', () => {
const url = `${TEST_HOST}/test/permalink`;
document.body.innerHTML = `
<a data-testid="permalink" class="js-data-file-blob-permalink-url" href="${url}"></a>
`;
setWindowLocation(url);
utils.moveToFilePermalink();
expect(windowHistorySpy).not.toHaveBeenCalled();
expect(document.title).toMatch(initialTitle);
});
it('should update history and title when URL is different and contains SHA', () => {
const testSha = 'ad9be38573f9ee4c4daec22673478c2dd1d81cd8';
document.body.innerHTML = `
<a class="js-data-file-blob-permalink-url" data-testid="permalink" href="/test/permalink/${testSha}"></a>
`;
utils.moveToFilePermalink();
expect(windowHistorySpy).toHaveBeenCalledWith({}, initialTitle, `/test/permalink/${testSha}`);
expect(document.title).toMatch(`Title · ${testSha}`);
});
it('should update history but not title when URL is different but contains no SHA', () => {
document.body.innerHTML = `
<a class="js-data-file-blob-permalink-url" data-testid="permalink" href="/test/permalink"></a>
`;
utils.moveToFilePermalink();
expect(windowHistorySpy).toHaveBeenCalledWith({}, initialTitle, `/test/permalink`);
expect(document.title).toMatch(initialTitle);
});
});
describe('shortcircuitPermalinkButton', () => {
let permalinkElement;
beforeEach(() => {
permalinkElement = document.createElement('a');
permalinkElement.dataset.testid = 'permalink';
permalinkElement.className = 'js-data-file-blob-permalink-url';
document.body.appendChild(permalinkElement);
});
afterEach(() => {
document.body.innerHTML = '';
jest.clearAllMocks();
});
it('attaches click event listener to permalink element', () => {
const addEventListenerSpy = jest.spyOn(permalinkElement, 'addEventListener');
utils.shortcircuitPermalinkButton();
expect(addEventListenerSpy).toHaveBeenCalledWith('click', expect.any(Function));
});
it('does nothing if permalink element is not found', () => {
document.body.innerHTML = '';
expect(() => {
utils.shortcircuitPermalinkButton();
}).not.toThrow();
});
describe('click handling', () => {
beforeEach(() => {
utils.shortcircuitPermalinkButton();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('prevents default and calls moveToFilePermalink for normal click', () => {
const clickEvent = new MouseEvent('click');
const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault');
const querySelectorSpy = jest.spyOn(document, 'querySelector');
permalinkElement.dispatchEvent(clickEvent);
expect(preventDefaultSpy).toHaveBeenCalled();
// Because we can't mock moveToFilePermalink, we are asserting it's being called by
// asserting that the first line inside the method is being executed:
expect(querySelectorSpy).toHaveBeenCalledWith('.js-data-file-blob-permalink-url');
});
it.each([
['ctrl', { ctrlKey: true }],
['meta', { metaKey: true }],
['shift', { shiftKey: true }],
])('does not prevent default or call moveToFilePermalink for %s click', (_, modifiers) => {
const clickEvent = new MouseEvent('click', {
...modifiers,
});
const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault');
const querySelectorSpy = jest.spyOn(document, 'querySelector');
permalinkElement.dispatchEvent(clickEvent);
expect(preventDefaultSpy).not.toHaveBeenCalled();
// Because we can't mock moveToFilePermalink, we are asserting it's being called by
// asserting that the first line inside the method is being executed:
expect(querySelectorSpy).not.toHaveBeenCalled();
});
});
});
});

View File

@ -67,9 +67,7 @@ describe('LabelActions', () => {
expect(deleteItem).toMatchObject({
text: 'Delete',
action: expect.any(Function),
extraAttrs: {
class: '!gl-text-red-500',
},
variant: 'danger',
});
});

View File

@ -35,7 +35,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'], feature_category: :code_revie
milestone assignees reviewers participants subscribed labels discussion_locked time_estimate
total_time_spent human_time_estimate human_total_time_spent reference author merged_at closed_at
commit_count current_user_todos conflicts auto_merge_enabled approved_by source_branch_protected
squash_on_merge available_auto_merge_strategies
squash_on_merge squash_read_only available_auto_merge_strategies
has_ci mergeable commits committers commits_without_merge_commits squash security_auto_fix default_squash_commit_message
auto_merge_strategy merge_user award_emoji prepared_at codequality_reports_comparer supports_lock_on_merge
mergeability_checks merge_after

View File

@ -963,4 +963,29 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
end
end
end
describe '#lazy_user_notes' do
it 'returns user notes lazily with 1 SQL query' do
project_work_item = create(:work_item)
create(:note_on_work_item, project: project_work_item.project, noteable: project_work_item)
group_work_item = create(:work_item, :group_level)
create(:note_on_work_item, namespace: project_work_item.namespace, noteable: group_work_item)
work_items = [project_work_item, group_work_item]
recorder = ActiveRecord::QueryRecorder.new(query_recorder_debug: true) do
work_items.each(&:lazy_user_notes)
work_items.each do |work_item|
expect(work_item.lazy_user_notes).to match_array(work_item.notes)
end
end
# from batch loader for lazy_user_notes
# 3 queries to find notes using each_batch
# when run in EE context, an additional query is made for the issues
# from spec itself
# 2 queries to load the notes to verify lazy_user_notes returns the right notes
expected_count = Gitlab.ee? ? 6 : 5
expect(recorder.count).to eq(expected_count)
end
end
end

View File

@ -696,7 +696,6 @@
- './ee/spec/helpers/path_locks_helper_spec.rb'
- './ee/spec/helpers/preferences_helper_spec.rb'
- './ee/spec/helpers/prevent_forking_helper_spec.rb'
- './ee/spec/helpers/projects_helper_spec.rb'
- './ee/spec/helpers/projects/on_demand_scans_helper_spec.rb'
- './ee/spec/helpers/projects/project_members_helper_spec.rb'
- './ee/spec/helpers/projects/security/dast_profiles_helper_spec.rb'