Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-28 18:14:05 +00:00
parent 82d72ee0ea
commit e7bfce6d9f
61 changed files with 235 additions and 262 deletions

View File

@ -280,7 +280,6 @@ export default {
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_count.vue',
'app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue',
'app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue',
'app/assets/javascripts/work_items/components/work_item_sticky_header.vue',
'ee/app/assets/javascripts/admin/subscriptions/show/components/subscription_breakdown.vue',
'ee/app/assets/javascripts/ai/components/duo_chat_feedback_modal.vue',
'ee/app/assets/javascripts/ai/components/user_feedback.vue',
@ -444,11 +443,6 @@ export default {
'ee/app/assets/javascripts/vulnerabilities/components/new_vulnerability/section_details.vue',
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_details.vue',
'ee/app/assets/javascripts/vulnerabilities/components/vulnerability_training.vue',
'ee/app/assets/javascripts/work_items/components/work_item_custom_fields_multi_select.vue',
'ee/app/assets/javascripts/work_items/components/work_item_custom_fields_single_select.vue',
'ee/app/assets/javascripts/work_items/components/work_item_health_status.vue',
'ee/app/assets/javascripts/work_items/components/work_item_iteration.vue',
'ee/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_health_status.vue',
],
rules: {
'vue/no-unused-properties': 'off',

View File

@ -158,7 +158,7 @@ db:check-schema-single-db:
db:check-migrations:
extends:
- .db-job-base
- .use-pg14 # Should match the db same version used by GDK
- .use-pg16 # Should match the db same version used by GDK
- .rails:rules:ee-and-foss-mr-with-migration
script:
- |

View File

@ -23,4 +23,4 @@
<!-- Make sure to add one of the type labels (as per https://handbook.gitlab.com/handbook/product/groups/product-analysis/engineering/metrics/#work-type-classification):-->
<!-- /label ~"type::bug" ~"type::feature" ~"type::tooling" ~"type::maintenance" -->
/label ~devops::analytics ~"group::analytics instrumentation"
/label ~"group::analytics instrumentation"

View File

@ -96,7 +96,6 @@ Gitlab/FeatureFlagWithoutActor:
- 'ee/app/views/projects/on_demand_scans/index.html.haml'
- 'ee/app/views/projects/settings/merge_requests/_merge_trains_settings.html.haml'
- 'ee/lib/api/code_suggestions.rb'
- 'ee/lib/api/internal/search/zoekt.rb'
- 'ee/lib/api/internal/suggested_reviewers.rb'
- 'ee/lib/ee/api/entities/application_setting.rb'
- 'ee/lib/ee/api/geo.rb'

View File

@ -1 +1 @@
2fb6e0b3a55ae7554b353ea70a54a97b7fe6512f
184f295d9e55a273ce85a2c3c32da526f32a8f6b

View File

@ -26,15 +26,7 @@ export default () => {
const isGroupPage = pageType === 'groups';
// This is a mini state to help the breadcrumb have the correct name in the details page
const breadCrumbState = Vue.observable({
name: '',
updateName(value) {
this.name = value;
},
});
const router = createRouter(endpoint, breadCrumbState);
const router = createRouter(endpoint);
const attachMainComponent = () =>
new Vue({
@ -50,7 +42,6 @@ export default () => {
npmGroupUrl,
projectListUrl,
groupListUrl,
breadCrumbState,
settingsPath,
canDeletePackages: parseBoolean(canDeletePackages),
},

View File

@ -84,7 +84,7 @@ export default {
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'],
inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl'],
trackingActions: {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
@ -117,11 +117,6 @@ export default {
error,
});
},
result() {
this.breadCrumbState.updateName(
`${this.packageEntity?.name} v${this.packageEntity?.version}`,
);
},
},
groupSettings: {
query: getGroupPackageSettings,

View File

@ -6,7 +6,7 @@ import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_regist
Vue.use(VueRouter);
export default function createRouter(base, breadCrumbState) {
export default function createRouter(base) {
const router = new VueRouter({
base,
mode: 'history',
@ -24,16 +24,9 @@ export default function createRouter(base, breadCrumbState) {
name: 'details',
path: '/:id',
component: Details,
meta: {
nameGenerator: () => breadCrumbState.name,
},
},
],
});
router.afterEach(() => {
breadCrumbState.updateName('');
});
return router;
}

View File

@ -20,7 +20,7 @@ export default {
return this.$route.name === this.rootRoute.name;
},
detailsRouteName() {
return this.detailsRoute.meta.nameGenerator();
return `${this.$route.params?.id}`;
},
isLoaded() {
return this.isRootRoute || this.detailsRouteName;
@ -35,7 +35,7 @@ export default {
if (!this.isRootRoute) {
crumbs.push({
text: this.detailsRouteName,
href: this.detailsRoute.meta.path,
href: this.detailsRoute.path,
});
}
return crumbs;

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { GlButton, GlTooltipDirective, GlIcon, GlAnimatedLoaderIcon } from '@gitlab/ui';
import { TYPE_ISSUE } from '~/issues/constants';
import { __, sprintf, s__ } from '~/locale';
import ReviewerAvatarLink from './reviewer_avatar_link.vue';
@ -44,6 +44,7 @@ export default {
components: {
GlButton,
GlIcon,
GlAnimatedLoaderIcon,
ReviewerAvatarLink,
},
directives: {
@ -216,7 +217,15 @@ export default {
:class="reviewStateIcon(user).class"
data-testid="reviewer-state-icon-parent"
>
<gl-animated-loader-icon
v-if="
user.type === 'DUO_CODE_REVIEW_BOT' &&
user.mergeRequestInteraction.reviewState === 'REVIEW_STARTED'
"
is-on
/>
<gl-icon
v-else
:size="reviewStateIcon(user).size || 16"
:name="reviewStateIcon(user).name"
:class="reviewStateIcon(user).iconClass"

View File

@ -11,6 +11,7 @@ query mergeRequestReviewers($fullPath: ID!, $iid: String!) {
nodes {
...User
...UserAvailability
type
mergeRequestInteraction {
canMerge
canUpdate

View File

@ -39,8 +39,8 @@ export const SEARCH_SCOPE = {
export const GLOBAL_COMMANDS_GROUP_TITLE = s__('CommandPalette|Global Commands');
export const USERS_GROUP_TITLE = s__('GlobalSearch|Users');
export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages');
export const PROJECTS_GROUP_TITLE = s__('GlobalSearch|Projects');
export const GROUPS_GROUP_TITLE = s__('GlobalSearch|Groups');
export const PROJECTS_GROUP_TITLE = s__("GlobalSearch|Projects I'm a member of");
export const GROUPS_GROUP_TITLE = s__("GlobalSearch|Groups I'm a member of");
export const ISSUES_GROUP_TITLE = s__('GlobalSearch|Issues');
export const PATH_GROUP_TITLE = s__('CommandPalette|Project files');
export const SETTINGS_GROUP_TITLE = s__('CommandPalette|Settings');
@ -75,3 +75,6 @@ export const OVERLAY_GOTO = s__('GlobalSearch|Go to %{kbdStart}↵%{kbdEnd}');
export const FREQUENTLY_VISITED_PROJECTS_HANDLE = 'FREQUENTLY_VISITED_PROJECTS_HANDLE';
export const FREQUENTLY_VISITED_GROUPS_HANDLE = 'FREQUENTLY_VISITED_GROUPS_HANDLE';
export const GROUPS_GROUP_HANDLE = 'Groups';
export const PROJECTS_GROUP_HANDLE = 'Projects';

View File

@ -37,6 +37,8 @@ import {
ISSUES_GROUP_TITLE,
PAGES_GROUP_TITLE,
GROUPS_GROUP_TITLE,
GROUPS_GROUP_HANDLE,
PROJECTS_GROUP_HANDLE,
} from '../command_palette/constants';
import SearchResultFocusLayover from './global_search_focus_overlay.vue';
import GlobalSearchNoResults from './global_search_no_results.vue';
@ -81,7 +83,7 @@ export default {
groups() {
return this.autocompleteGroupedSearchOptions.map((group) => {
return {
name: group?.name,
name: this.modifiedGroupName(group?.name),
items: group?.items?.map((item) => {
return {
...item,
@ -160,6 +162,17 @@ export default {
}
}
},
modifiedGroupName(groupName) {
if (groupName === GROUPS_GROUP_HANDLE) {
return this.$options.i18n.GROUPS_GROUP_TITLE;
}
if (groupName === PROJECTS_GROUP_HANDLE) {
return this.$options.i18n.PROJECTS_GROUP_TITLE;
}
return groupName;
},
},
AVATAR_SHAPE_OPTION_RECT,
};

View File

@ -162,9 +162,7 @@ export default {
<template #description>
<gl-sprintf
:message="
s__(
'CICD|Authentication events from the last 30 days. %{linkStart}Learn more.%{linkEnd}',
)
s__('CICD|Authentication events using the job token. %{linkStart}Learn more.%{linkEnd}')
"
>
<template #link="{ content }">
@ -214,7 +212,7 @@ export default {
class="gl-text-center"
data-testid="auth-logs-no-events"
>
{{ s__('CICD|No authentication events in the last 30 days.') }}
{{ s__('CICD|No authentication events to display.') }}
</div>
</crud-component>
<gl-keyset-pagination

View File

@ -1051,7 +1051,6 @@ export default {
:work-item-type="selectedWorkItemTypeName"
:custom-fields="workItemCustomFields"
:full-path="selectedProjectFullPath"
:is-group="isGroup"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>

View File

@ -330,7 +330,6 @@ export default {
:custom-fields="customFields"
:full-path="fullPath"
:can-update="canUpdateMetadata"
:is-group="isGroup"
@error="$emit('error', $event)"
/>
<work-item-parent

View File

@ -896,21 +896,10 @@ export default {
v-if="showIntersectionObserver"
:current-user-todos="currentUserTodos"
:show-work-item-current-user-todos="showWorkItemCurrentUserTodos"
:parent-work-item-confidentiality="parentWorkItemConfidentiality"
:update-in-progress="updateInProgress"
:full-path="workItemFullPath"
:is-modal="isModal"
:work-item="workItem"
:is-sticky-header-showing="isStickyHeaderShowing"
:work-item-notifications-subscribed="workItemNotificationsSubscribed"
:work-item-author-id="workItemAuthorId"
:is-group="isGroupWorkItem"
:allowed-child-types="allowedChildTypes"
:parent-id="parentWorkItemId"
:namespace-full-name="namespaceFullName"
:has-children="hasChildren"
:show-sidebar="showSidebar"
:truncation-enabled="truncationEnabled"
@hideStickyHeader="hideStickyHeader"
@showStickyHeader="showStickyHeader"
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"

View File

@ -27,10 +27,6 @@ export default {
type: Object,
required: true,
},
fullPath: {
type: String,
required: true,
},
isStickyHeaderShowing: {
type: Boolean,
required: true,
@ -44,63 +40,16 @@ export default {
required: false,
default: false,
},
parentWorkItemConfidentiality: {
type: Boolean,
required: false,
default: false,
},
parentId: {
type: String,
required: false,
default: null,
},
showWorkItemCurrentUserTodos: {
type: Boolean,
required: false,
default: false,
},
isModal: {
type: Boolean,
required: false,
default: false,
},
currentUserTodos: {
type: Array,
required: false,
default: () => [],
},
workItemAuthorId: {
type: Number,
required: false,
default: 0,
},
isGroup: {
type: Boolean,
required: true,
},
allowedChildTypes: {
type: Array,
required: false,
default: () => [],
},
namespaceFullName: {
type: String,
required: false,
default: '',
},
hasChildren: {
type: Boolean,
required: false,
default: false,
},
showSidebar: {
type: Boolean,
required: true,
},
truncationEnabled: {
type: Boolean,
required: true,
},
},
computed: {
canUpdate() {

View File

@ -233,7 +233,13 @@ class Issue < ApplicationRecord
scope :confidential_only, -> { where(confidential: true) }
scope :without_hidden, -> {
where('NOT EXISTS (?)', Users::BannedUser.select(1).where('issues.author_id = banned_users.user_id'))
if Feature.enabled?(:optimize_issues_banned_users_query, :instance)
# We add `+ 0` to the author_id to make the query planner use a nested loop and prevent
# loading of all banned user IDs for certain queries
where_not_exists(Users::BannedUser.where('banned_users.user_id = (issues.author_id + 0)'))
else
where_not_exists(Users::BannedUser.where('banned_users.user_id = issues.author_id'))
end
}
scope :counts_by_state, -> { reorder(nil).group(:state_id).count }

View File

@ -473,7 +473,6 @@ class User < ApplicationRecord
delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
delegate :bluesky, :bluesky=, to: :user_detail, allow_nil: true
delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true

View File

@ -3,8 +3,6 @@
class UserDetail < ApplicationRecord
extend ::Gitlab::Utils::Override
REGISTRATION_OBJECTIVE_PAIRS = { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5, joining_team: 6 }.freeze
belongs_to :user
belongs_to :bot_namespace, class_name: 'Namespace', optional: true, inverse_of: :bot_user_details
@ -15,6 +13,8 @@ class UserDetail < ApplicationRecord
validate :bot_namespace_user_type, if: :bot_namespace_id_changed?
ignore_column :registration_objective, remove_after: '2025-07-17', remove_with: '18.2'
DEFAULT_FIELD_LENGTH = 500
# specification for bluesky identifier https://web.plc.directory/spec/v0.1/did-plc
@ -56,8 +56,6 @@ class UserDetail < ApplicationRecord
before_validation :sanitize_attrs
before_save :prevent_nil_fields
enum :registration_objective, REGISTRATION_OBJECTIVE_PAIRS, suffix: true
def sanitize_attrs
%i[bluesky discord linkedin mastodon skype twitter website_url].each do |attr|
value = self[attr]

View File

@ -0,0 +1,10 @@
---
name: optimize_issues_banned_users_query
description: Optimize banned user clause for issue list queries
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/394980
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189141
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/537923
milestone: '18.0'
group: group::project management
type: gitlab_com_derisk
default_enabled: false

View File

@ -35,6 +35,10 @@ approval_merge_request_rules:
- table: approval_policy_rules
column: approval_policy_rule_id
on_delete: async_nullify
ci_build_needs:
- table: projects
column: project_id
on_delete: async_delete
ci_build_report_results:
- table: projects
column: project_id

View File

@ -8,15 +8,6 @@ description: Dependencies for a specific CI/CD job.
introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31328
milestone: '12.2'
gitlab_schema: gitlab_ci
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: build_id
table: p_ci_builds
sharding_key: project_id
belongs_to: build
foreign_key_name: fk_rails_3cf221d4ed_p
desired_sharding_key_migration_job_name: BackfillCiBuildNeedsProjectId
sharding_key:
project_id: projects
table_size: over_limit

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddAuthorEmailToSshSignatures < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '18.0'
# rubocop:disable Migration/AddLimitToTextColumns -- limit is added in a separate migration 20250425111203
def up
add_column :ssh_signatures, :author_email, :text
end
# rubocop:enable Migration/AddLimitToTextColumns
def down
remove_column :ssh_signatures, :author_email if column_exists?(:ssh_signatures, :author_email)
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddAuthorEmailLimitToSshSignatures < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '18.0'
def up
add_text_limit :ssh_signatures, :author_email, 255
end
def down
remove_text_limit :ssh_signatures, :author_email
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class ValidateCiBuildNeedsProjectIdNotNull < Gitlab::Database::Migration[2.2]
milestone '18.0'
def up
validate_not_null_constraint :ci_build_needs, :project_id, constraint_name: 'check_4fab85ecdc'
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
49bb1fbe7f7055a16c167199fe7c5c1dd62e0c46e68f50dff05f2470b22241f6

View File

@ -0,0 +1 @@
a07dd290fe3abc6c4e74f41ec0a83218b4ef8cc8c83a99e0da820fce11c7c732

View File

@ -0,0 +1 @@
3672a1886f77ed6e9072ef2e4bb2f1b00f87e1b9f7c7289bc930db1f0f4e376d

View File

@ -10912,7 +10912,8 @@ CREATE TABLE ci_build_needs (
build_id bigint NOT NULL,
partition_id bigint NOT NULL,
id bigint NOT NULL,
project_id bigint
project_id bigint,
CONSTRAINT check_4fab85ecdc CHECK ((project_id IS NOT NULL))
);
CREATE SEQUENCE ci_build_needs_id_seq
@ -23245,7 +23246,9 @@ CREATE TABLE ssh_signatures (
verification_status smallint DEFAULT 0 NOT NULL,
commit_sha bytea NOT NULL,
user_id bigint,
key_fingerprint_sha256 bytea
key_fingerprint_sha256 bytea,
author_email text,
CONSTRAINT check_5ff707c7f9 CHECK ((char_length(author_email) <= 255))
);
CREATE SEQUENCE ssh_signatures_id_seq
@ -29140,9 +29143,6 @@ ALTER TABLE security_scans
ALTER TABLE vulnerability_scanners
ADD CONSTRAINT check_37608c9db5 CHECK ((char_length(vendor) <= 255)) NOT VALID;
ALTER TABLE ci_build_needs
ADD CONSTRAINT check_4fab85ecdc CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE ONLY instance_type_ci_runners
ADD CONSTRAINT check_5c34a3c1db UNIQUE (id);

View File

@ -165,7 +165,7 @@ After you sign in to Switchboard, follow these steps to create your instance:
- **Reference architecture**: The maximum number of users allowed in your instance. For more information, see [availability and scalability](../../../subscriptions/gitlab_dedicated/data_residency_and_high_availability.md#availability-and-scalability). For example, up to 3,000 users.
- **Total repository capacity**: The total storage space available for all repositories in your instance. For example, 16 GB. This setting cannot be reduced after you create your instance. You can increase storage capacity later if needed.
- **Total repository capacity**: The total storage space available for all repositories in your instance. For example, 16 GB. This setting cannot be reduced after you create your instance. You can increase storage capacity later if needed. For more information about how storage is calculated for GitLab Dedicated, see [GitLab Dedicated storage types](storage_types.md).
If you need to change either of these values, [submit a support ticket](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=4414917877650).

View File

@ -35,7 +35,7 @@ The **Overview** page displays:
The top section shows important information about your tenant, including:
- Tenant name and URL
- Total Git repository capacity
- [Total Git repository capacity](create_instance/storage_types.md#view-repository-storage-per-gitaly-node)
- Current GitLab version
- Reference architecture
- Maintenance window

View File

@ -93,8 +93,8 @@ Test the regex patterns carefully. Tool output formats can change over time, and
| Tool | Command | Regex pattern |
|------|--------------------------------------|---------------|
| tap | `tap --coverage-report=text-summary` | `/^Statements\s*:\s*([^%]+)/` |
| nyc | `nyc npm test` | `/All files[^\|]*\|[^\|]*\s+([\d\.]+)/` |
| jest | `jest --ci --coverage` | `/All files[^\|]*\|[^\|]*\s+([\d\.]+)/` |
| nyc | `nyc npm test` | `/All files[^\|]*\\|[^\|]*\s+([\d\.]+)/` |
| jest | `jest --ci --coverage` | `/All files[^\|]*\\|[^\|]*\s+([\d\.]+)/` |
{{< /tab >}}

View File

@ -64,12 +64,12 @@ with the deployed staging AI gateway. To do this:
### Setup instructions to use GDK with the Code Suggestions Add-on
**Option 1 - Recommended**
#### Option 1 - Recommended
1. Ensure that you have a [GitLab Team Member License](https://handbook.gitlab.com/handbook/engineering/developer-onboarding/#working-on-gitlab-ee-developer-licenses) and that it is [activated](../../administration/license_file.md).
1. Follow the [Setup and Run GDK](_index.md#set-up-and-run-gdk) guide under the AI Features doc.
**Option 2**
#### Option 2
You can set up Duo on your GDK by going through CustomersDot. This is a more complex process, but it more accurately reflects the GitLab Self-Managed setup of our customers.

View File

@ -222,17 +222,6 @@ Optionally, the context can contain:
- `namespace`. If not provided, `project.namespace` will be used (if `project` is available).
- `category`
- `additional_properties`
- `event_attribute_overrides` - is used when its necessary to override the attributes available in parent context. For example:
```ruby
let(:event) { 'create_new_issue' }
it_behaves_like 'internal event tracking' do
let(:event_attribute_overrides) { { event: 'create_new_milestone'} }
subject(:service_action) { described_class.new(issue).save }
end
```
If present in the context, the following legacy options will be respected by the shared example but are discouraged:

View File

@ -17,7 +17,7 @@ For each of the vulnerabilities listed in this document, AppSec aims to have a S
| Guideline | Rule | Status |
|---|---|---|
| [Regular Expressions](#regular-expressions-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_regex.yml) | ✅ |
| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml) | ✅ |
| [ReDOS](#denial-of-service-redos--catastrophic-backtracking) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_redos_2.yml), [3](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/merge_requests/59#note_2443657926) | ✅ |
| [JWT](#json-web-tokens-jwt) | Pending | ❌ |
| [SSRF](#server-side-request-forgery-ssrf) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_url-1.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_insecure_http.yml?ref_type=heads) | ✅ |
| [XSS](#xss-guidelines) | [1](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_redirect.yml), [2](https://gitlab.com/gitlab-com/gl-security/product-security/appsec/sast-custom-rules/-/blob/main/secure-coding-guidelines/ruby/ruby_xss_html_safe.yml) | ✅ |
@ -301,6 +301,37 @@ For other regular expressions, here are a few guidelines:
Go's [`regexp`](https://pkg.go.dev/regexp) package uses `re2` and isn't vulnerable to backtracking issues.
#### Python Regular Expression Denial of Service (ReDoS) Prevention
Python offers three main regular expression libraries:
| Library | Security | Notes |
|---------|---------------------|-----------------------------------------------------------------------|
| `re` | Vulnerable to ReDoS | Built-in library. Must use timeout parameter. |
| `regex` | Vulnerable to ReDoS | Third-party library with extended features. Must use timeout parameter. |
| `re2` | Secure by default | Wrapper for the Google RE2 engine. Prevents backtracking by design. |
Both `re` and `regex` use backtracking algorithms that can cause exponential execution time with certain patterns.
```python
evil_input = 'a' * 30 + '!'
# Vulnerable - can cause exponential execution time with nested quantifiers
# 30 'a's -> ~30 seconds
# 31 'a's -> ~60 seconds
re.match(r'^(a+)+$', evil_input)
regex.match(r'^(a|aa)+$', evil_input)
# Secure - adds timeout to limit execution time
re.match(r'^(a+)+$', evil_input, timeout=1.0)
regex.match(r'^(a|aa)+$', evil_input, timeout=1.0)
# Preferred - re2 prevents catastrophic backtracking by design
re2.match(r'^(a+)+$', evil_input)
```
When working with regular expressions in Python, use `re2` when possible or always include timeouts with `re` and `regex`.
### Further Links
- [Rubular](https://rubular.com/) is a nice online tool to fiddle with Ruby Regexps.

View File

@ -50,6 +50,8 @@ Workflow:
For a click-through demo, see [GitLab Duo Workflow](https://gitlab.navattic.com/duo-workflow).
<!-- Demo published on 2025-03-18 -->
For an overview, watch <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Enhancing your quality assurance with GitLab Duo Workflow](https://youtu.be/Tuj7TgqY81Q?si=IbxaKv7IhAHYnHkN). <!-- Video published on 2025-03-20-->
## Prerequisites
Before you can use Workflow, you must:
@ -183,3 +185,7 @@ On your GitLab Self-Managed instance, you can view these events on the
Workflow is a private beta and your feedback is crucial to improve it for you and others.
To report issues or suggest improvements,
[complete this survey](https://gitlab.fra1.qualtrics.com/jfe/form/SV_9GmCPTV7oH9KNuu).
## Related topics
- [Use GitLab Duo Workflow to improve application quality assurance](https://about.gitlab.com/blog/2025/04/10/use-gitlab-duo-workflow-to-improve-application-quality-assurance/)

View File

@ -77,12 +77,6 @@ To add an emoji reaction to a comment or description:
To use them in a text box, type the filename between two colons.
For example, `:thank-you:`.
You can upload custom emoji to a GitLab instance with the GraphQL API.
For more information, see [Use custom emoji with GraphQL](../api/graphql/custom_emoji.md).
For a list of custom emoji available for GitLab.com, see
[the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img).
### Upload custom emoji to a group
{{< history >}}

View File

@ -164,7 +164,7 @@ Code Suggestions is aware of the context you're working in.
| [Open tab files](#using-open-files-as-context) | Files open in tabs in your IDE. These files give GitLab Duo more information about the standards and practices in your code project. | Optional, but on by default. |
| [Imported files](#using-imported-files-as-context) | Files imported in the current opened file. These imported files give GitLab Duo more information about the classes and methods used in the current file. | Optional and off by default. |
**Footnotes:**
Footnotes:
1. Code completion is aware of all [supported languages](supported_extensions.md#supported-languages-by-ide).
Code generation is aware of files in these languages only:
@ -321,10 +321,10 @@ When using Code Suggestions, [code review best practice](../../../../development
To learn about the code that builds the prompt, see these files:
- **Code generation**:
- Code generation:
[`ee/lib/api/code_suggestions.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/api/code_suggestions.rb#L76)
in the `gitlab` repository.
- **Code completion**:
- Code completion:
[`ai_gateway/code_suggestions/processing/completions.py`](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist/-/blob/fcb3f485a8f047a86a8166aad81f93b6d82106a7/ai_gateway/code_suggestions/processing/completions.py#L273)
in the `modelops` repository.

View File

@ -91,7 +91,7 @@ The Repository X-Ray searches a maximum of two directory levels from the reposit
| Python | Poetry | `poetry.lock`, `pyproject.toml` | 17.5 or later |
| Ruby | RubyGems | `Gemfile.lock` | 17.4 or later |
**Footnotes**:
Footnotes:
1. For Python Pip, all configuration files matching the `*requirements*.txt` glob pattern are processed.

View File

@ -156,7 +156,7 @@ To do this:
1. Find your desired language in the list of
[language identifiers](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem).
You need the **Identifier** for your languages in a later step.
You need the identifier for your languages in a later step.
1. In your IDE, on the top bar, select your IDE name, then select **Settings**.
1. On the left sidebar, select **Tools > GitLab Duo**.
1. Under **Code Suggestions Enabled Languages > Additional languages**, add the identifier for each language

View File

@ -191,7 +191,7 @@ For non-Code Suggestions troubleshooting for Microsoft Visual Studio, see
### IntelliCode is missing
Code Suggestions requires the **IntelliCode** component of Visual Studio. If the component
Code Suggestions requires the IntelliCode component of Visual Studio. If the component
is missing, you might see an error like this when you start Visual Studio:
```plaintext
@ -211,7 +211,7 @@ but found 0 after applying applicable constraints.
[...]
```
To fix this problem, install the **IntelliCode** component:
To fix this problem, install the IntelliCode component:
1. In the Windows start menu, search for the **Visual Studio Installer** and open it.
1. Select your Visual Studio instance, then select **Modify**.

View File

@ -11629,7 +11629,7 @@ msgstr ""
msgid "CICD|Are you sure you want to remove %{namespace} from the job token allowlist? This action cannot be undone."
msgstr ""
msgid "CICD|Authentication events from the last 30 days. %{linkStart}Learn more.%{linkEnd}"
msgid "CICD|Authentication events using the job token. %{linkStart}Learn more.%{linkEnd}"
msgstr ""
msgid "CICD|Authentication log"
@ -11728,7 +11728,7 @@ msgstr ""
msgid "CICD|Maintainer"
msgstr ""
msgid "CICD|No authentication events in the last 30 days."
msgid "CICD|No authentication events to display."
msgstr ""
msgid "CICD|No resources selected (minimal access only)"
@ -28487,6 +28487,9 @@ msgstr ""
msgid "GlobalSearch|Groups"
msgstr ""
msgid "GlobalSearch|Groups I'm a member of"
msgstr ""
msgid "GlobalSearch|Help"
msgstr ""
@ -28598,6 +28601,9 @@ msgstr ""
msgid "GlobalSearch|Projects"
msgstr ""
msgid "GlobalSearch|Projects I'm a member of"
msgstr ""
msgid "GlobalSearch|Projects not indexed"
msgstr ""
@ -39502,9 +39508,6 @@ msgstr ""
msgid "NamespaceStorageSize|If %{namespace_name} exceeds the %{storage_docs_link_start}storage quota%{link_end}, your ability to write new data to this namespace will be restricted. %{read_only_link_start}Which actions become restricted?%{link_end}"
msgstr ""
msgid "NamespaceStorageSize|If a project reaches 100%% of the %{storage_docs_link_start}storage quota%{link_end} (%{free_size_limit}) the project will be in a read-only state, and you won't be able to push to your repository or add large files."
msgstr ""
msgid "NamespaceStorageSize|To manage your usage and prevent your projects from being placed in a read-only state, you should immediately %{manage_storage_link_start}reduce storage%{link_end}, or %{support_link_start}contact support%{link_end} to help you manage your usage."
msgstr ""
@ -39523,16 +39526,22 @@ msgstr ""
msgid "NamespaceStorageSize|To remove the read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or %{purchase_more_link_start}purchase more storage%{link_end}."
msgid "NamespaceStorageSize|To remove the read-only state contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
msgid "NamespaceStorageSize|To remove the read-only state, %{manage_storage_link_start}manage your storage usage%{link_end} or %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
msgid "NamespaceStorageSize|To remove the read-only state, %{manage_storage_link_start}manage your storage usage%{link_end} or %{support_link_start}contact support%{link_end}."
msgstr ""
msgid "NamespaceStorageSize|We've noticed an unusually high storage usage on %{namespace_name}"
msgstr ""
msgid "NamespaceStorageSize|You have consumed all available %{storage_docs_link_start}storage%{link_end} and you can't push or add large files to projects over the free tier limit (%{free_size_limit})."
msgid "NamespaceStorageSize|When a project reaches 100%% of the %{storage_docs_link_start}allocated storage%{link_end} (%{free_size_limit}) it will be placed in a read-only state. You won't be able to push and add large files to your repository."
msgstr ""
msgid "NamespaceStorageSize|You have consumed all available %{storage_docs_link_start}storage%{link_end} and can't push or add large files to projects over the included storage (%{free_size_limit})."
msgstr ""
msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} for %{namespace_name}"

View File

@ -53,11 +53,8 @@ module QA
end
def run_unregister_command!
cmd = <<~CMD.tr("\n", ' ')
docker exec --detach #{@name} sh -c "#{unregister_command}"
CMD
shell(cmd, mask_secrets: [runner_auth_token])
output = shell("docker exec #{@name} sh -c '#{unregister_command}'", mask_secrets: [runner_auth_token])
confirm_unregistered(output)
end
def tags=(tags)
@ -115,13 +112,21 @@ module QA
def runner_auth_token
runner_list = shell("docker exec #{@name} sh -c 'gitlab-runner list'")
runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1)
runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1) || raise("No token found in runner list output")
end
def unregister_command
"gitlab-runner unregister --url #{@address} --token #{runner_auth_token}"
end
def unregister_message_pattern
/Unregistering runner( manager)? from GitLab succeeded/
end
def confirm_unregistered(output)
raise("Failed to unregister. Shell response: #{output}") unless output&.match?(unregister_message_pattern)
end
# Ping Cloudflare DNS, should fail
# Ping Registry, should fail to resolve
def prove_airgap

View File

@ -11,10 +11,6 @@ module QA
end
it 'user unregisters a runner with authentication token',
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/513860',
type: :stale
},
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/510652' do
Flow::Login.sign_in
@ -28,16 +24,8 @@ module QA
end
end
# The output of the unregister command is verified inside the GitlabRunner class
runner.unregister!
page.refresh
Page::Project::Settings::CiCd.perform do |settings|
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
expect(page).to have_offline_runner
end
end
end
end
end

View File

@ -202,9 +202,10 @@ module QA
describe '#unregister!' do
let(:run_unregister_command) { subject.send(:run_unregister_command!) }
let(:unregister_message) { 'Unregistering runner manager from GitLab succeeded' }
before do
allow(subject).to receive(:shell)
allow(subject).to receive(:shell).and_return(unregister_message)
subject.instance_eval do
def runner_auth_token

View File

@ -7,7 +7,7 @@
require 'parallel'
require 'rainbow'
UNUSED_METHODS = 56
UNUSED_METHODS = 52
print_output = %w[true 1].include? ENV["REPORT_ALL_UNUSED_METHODS"]

View File

@ -86,7 +86,7 @@ RSpec.describe 'Container Registry', :js, feature_category: :container_registry
end
it 'shows the details breadcrumb' do
expect(find_by_testid('breadcrumb-links')).to have_link 'my/image'
expect(find_by_testid('breadcrumb-links')).to have_link 'Container registry'
end
it 'shows the image title' do

View File

@ -124,7 +124,7 @@ RSpec.describe 'Container Registry', :js, feature_category: :container_registry
end
it 'shows the details breadcrumb' do
expect(find_by_testid('breadcrumb-links')).to have_link 'my/image'
expect(find_by_testid('breadcrumb-links')).to have_link 'Container registry'
end
it 'shows the image title' do

View File

@ -336,15 +336,6 @@ describe('PackagesApp', () => {
});
});
it('calls the appropriate function to set the breadcrumbState', async () => {
const { name, version } = packageData();
createComponent();
await waitForPromises();
expect(breadCrumbState.updateName).toHaveBeenCalledWith(`${name} v${version}`);
});
describe('delete package', () => {
const originalReferrer = document.referrer;
const setReferrer = (value = packageDetailsQuery().data.package.project.name) => {

View File

@ -53,16 +53,13 @@ describe('Registry Breadcrumb', () => {
});
it('passes root and details to `items` prop', () => {
expect(wrapper.findComponent(GlBreadcrumb).props('items')).toEqual([
{
text: 'mock name',
to: '/',
},
{
text: 'mock name',
href: '/details',
},
]);
const breadcrumbItems = wrapper.findComponent(GlBreadcrumb).props('items');
expect(breadcrumbItems).toHaveLength(2);
expect(breadcrumbItems[0]).toEqual({
text: 'mock name',
to: '/',
});
expect(breadcrumbItems[1].href).toBe('/:id');
});
});
});

View File

@ -274,15 +274,15 @@ describe('GlobalSearchAutocompleteItems', () => {
describe('tracking', () => {
it.each`
action | event
${'Projects'} | ${EVENT_CLICK_PROJECT_RESULT_IN_COMMAND_PALETTE}
${'Groups'} | ${EVENT_CLICK_GROUP_RESULT_IN_COMMAND_PALETTE}
${'Merge requests'} | ${EVENT_CLICK_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
${'Issues'} | ${EVENT_CLICK_ISSUE_RESULT_IN_COMMAND_PALETTE}
${'Recent issues'} | ${EVENT_CLICK_RECENT_ISSUE_RESULT_IN_COMMAND_PALETTE}
${'Recent epics'} | ${EVENT_CLICK_RECENT_EPIC_RESULT_IN_COMMAND_PALETTE}
${'Recent merge requests'} | ${EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
${undefined} | ${EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE}
action | event
${"Projects I'm a member of"} | ${EVENT_CLICK_PROJECT_RESULT_IN_COMMAND_PALETTE}
${"Groups I'm a member of"} | ${EVENT_CLICK_GROUP_RESULT_IN_COMMAND_PALETTE}
${'Merge requests'} | ${EVENT_CLICK_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
${'Issues'} | ${EVENT_CLICK_ISSUE_RESULT_IN_COMMAND_PALETTE}
${'Recent issues'} | ${EVENT_CLICK_RECENT_ISSUE_RESULT_IN_COMMAND_PALETTE}
${'Recent epics'} | ${EVENT_CLICK_RECENT_EPIC_RESULT_IN_COMMAND_PALETTE}
${'Recent merge requests'} | ${EVENT_CLICK_RECENT_MERGE_REQUEST_RESULT_IN_COMMAND_PALETTE}
${undefined} | ${EVENT_CLICK_USER_RESULT_IN_COMMAND_PALETTE}
`(
"triggers tracking event '$event' after emiting action '$action'",
({ action, event }) => {

View File

@ -87,9 +87,7 @@ describe('TokenAccess component', () => {
await waitForPromises();
expect(findCrudComponentBody().text()).toContain(
'No authentication events in the last 30 days.',
);
expect(findCrudComponentBody().text()).toContain('No authentication events to display.');
});
it('displays a table when data is available', async () => {

View File

@ -1222,13 +1222,6 @@ describe('WorkItemDetail component', () => {
describe('work item parent id', () => {
const parentId = 'gid://gitlab/Issue/1';
it('passes the `parentWorkItemId` value down to the `WorkItemStickyHeader` component', async () => {
createComponent();
await waitForPromises();
expect(findStickyHeader().props('parentId')).toBe(parentId);
});
it('passes the `parentWorkItemId` value down to the `WorkItemActions` component', async () => {
createComponent();
await waitForPromises();

View File

@ -18,7 +18,6 @@ describe('WorkItemStickyHeader', () => {
discussionLocked = false,
canUpdate = true,
features = {},
parentId = null,
movedToWorkItemUrl = null,
duplicatedToWorkItemUrl = null,
promotedToEpicUrl = null,
@ -34,19 +33,12 @@ describe('WorkItemStickyHeader', () => {
duplicatedToWorkItemUrl,
promotedToEpicUrl,
}).data.workItem,
fullPath: '/test',
isStickyHeaderShowing: true,
workItemNotificationsSubscribed: true,
updateInProgress: false,
parentWorkItemConfidentiality: false,
showWorkItemCurrentUserTodos: true,
isModal: false,
currentUserTodos: [],
workItemState: STATE_OPEN,
isGroup: false,
parentId,
showSidebar: true,
truncationEnabled: true,
},
provide: {
glFeatures: {

View File

@ -6,11 +6,6 @@ RSpec.describe UserDetail, feature_category: :system_access do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:bot_namespace).inverse_of(:bot_user_details) }
specify do
values = [:basics, :move_repository, :code_storage, :exploring, :ci, :other, :joining_team]
is_expected.to define_enum_for(:registration_objective).with_values(values).with_suffix
end
describe 'validations' do
context 'for onboarding_status json schema' do
let(:step_url) { '_some_string_' }

View File

@ -135,9 +135,6 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to delegate_method(:bio).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:bio=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:registration_objective).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:registration_objective=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:discord).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:discord=).to(:user_detail).with_arguments(:args).allow_nil }

View File

@ -245,12 +245,22 @@ RSpec.describe API::Todos, feature_category: :source_code_management do
end
context 'when user is a bot' do
it_behaves_like 'internal event tracking' do
let(:event) { 'request_todos_by_bot_user' }
let(:user) { create(:user, :service_account) }
let(:additional_properties) { { label: 'user_type', property: user.user_type } }
let(:event_attribute_overrides) { { project: nil, namespace: nil } }
subject(:api_request) { get api('/todos', user) }
let(:user) { create(:user, :service_account) }
it "triggers an internal event" do
expect { get api('/todos', user) }
.to trigger_internal_events('request_todos_by_bot_user')
.with(
category: 'InternalEventTracking',
user: user,
additional_properties: {
label: 'user_type',
property: user.user_type
}
).and increment_usage_metrics(
'redis_hll_counters.count_distinct_user_id_from_request_todos_by_bot_user_weekly',
'redis_hll_counters.count_distinct_user_id_from_request_todos_by_bot_user_monthly'
)
end
end

View File

@ -9,7 +9,6 @@
# - namespace
# - category
# - additional_properties
# - event_attribute_overrides - is used when its necessary to override the attributes available in parent context.
#
# [Legacy] If present in the context, the following will be respected by the shared example but are discouraged:
# - label
@ -57,7 +56,7 @@ RSpec.shared_examples 'internal event tracking' do
value: try(:value)
}.compact
}
}.merge(try(:event_attribute_overrides) || {})
}
expect { subject }
.to trigger_internal_events(event)

View File

@ -512,10 +512,6 @@ RSpec.describe 'Internal Events matchers', :clean_gitlab_redis_shared_state, fea
let(:label) { expected_label }
end
it_behaves_like 'internal event tracking' do
let(:event_attribute_overrides) { { additional_properties: { label: expected_label } } }
end
context 'with incorrect value being provided in additional_properties.' do
let(:unexpected_label) { 'BAD label value' }
@ -531,10 +527,6 @@ RSpec.describe 'Internal Events matchers', :clean_gitlab_redis_shared_state, fea
it_behaves_like 'internal event tracking' do
let(:label) { unexpected_label }
end
it_behaves_like 'internal event tracking' do
let(:event_attribute_overrides) { { additional_properties: { label: unexpected_label } } }
end
end
end