Add latest changes from gitlab-org/gitlab@master
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -138,9 +138,7 @@ export default function initHeaderApp({ router, isReadmeView = false, isBlobView
|
|||
props: {
|
||||
refType,
|
||||
currentRef: ref,
|
||||
// BlobControls:
|
||||
projectPath,
|
||||
// RefSelector:
|
||||
projectId,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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:')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
ff25d3c1a1f25af9447a6463ff867ac4d29741f440a5b2bc91d757cbb9aa77f6
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 >}}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
|
|
@ -230,13 +230,13 @@ To change how a merge request shows changed lines:
|
|||
|
||||
{{< tab title="Inline changes" >}}
|
||||
|
||||

|
||||

|
||||
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab title="Side-by-side changes" >}}
|
||||
|
||||

|
||||

|
||||
|
||||
{{< /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" >}}):
|
||||

|
||||
|
||||
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:
|
||||

|
||||
1. Scroll to the line the comment is attached to. In the gutter margin, select the user avatar:
|
||||

|
||||
|
||||
## 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**:
|
||||
|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -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:
|
||||
|
||||

|
||||
all matching tags.
|
||||
|
||||
## Prevent tag creation with the same name as branches
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -53837,6 +53837,9 @@ msgstr ""
|
|||
msgid "Selected commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "Selected database"
|
||||
msgstr ""
|
||||
|
||||
msgid "Selected for all items."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,9 +67,7 @@ describe('LabelActions', () => {
|
|||
expect(deleteItem).toMatchObject({
|
||||
text: 'Delete',
|
||||
action: expect.any(Function),
|
||||
extraAttrs: {
|
||||
class: '!gl-text-red-500',
|
||||
},
|
||||
variant: 'danger',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||