Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-02 09:12:36 +00:00
parent b57bbf5a40
commit dce429bdac
95 changed files with 2016 additions and 1566 deletions

View File

@ -461,6 +461,12 @@ Gitlab/EventStoreSubscriber:
- 'spec/**/*'
- 'ee/spec/**/*'
# See https://gitlab.com/gitlab-org/gitlab/-/issues/496562
Gitlab/Ai/OrderConstants:
Enabled: true
Include:
- 'ee/lib/ai/context/dependencies/config_files/constants.rb'
Gitlab/DocumentationLinks/HardcodedUrl:
Enabled: true
Exclude:

View File

@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 17.10.2 (2025-04-02)
### Fixed (2 changes)
- [Fix free push limit on non-saas](https://gitlab.com/gitlab-org/gitlab/-/commit/41d60e463f147b2cc76889e0f97a3192a9654ec9) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186166))
- [Ensure runner taggings are copied from taggings](https://gitlab.com/gitlab-org/gitlab/-/commit/225b22847600e53bcf83d26b85e0a5e80b38c470) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186279))
### Other (1 change)
- [No-op ci_runner_machines_687967fa8a table backfill migration](https://gitlab.com/gitlab-org/gitlab/-/commit/5e9c7c787a1fb500707fbbceea2dccd1fd86ab92) ([merge request](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185304))
## 17.10.1 (2025-03-26)
### Security (7 changes)

View File

@ -1 +1 @@
0.0.29
0.0.30

View File

@ -1 +1 @@
f204b12d2babba4d5d4bbe63fc5d1a2cf1e4c276
6a91a094bd42c5877c1b9cf782ce49d55186ad0d

View File

@ -1 +1 @@
4c89bc3a1957b7f6766a5dab1446c45721d96753
71a2331de156ab113749abc7cb82f6439b0436f9

View File

@ -117,16 +117,13 @@ export default {
const packageTypeOptions = [
{ value: 'NPM', text: s__('PackageRegistry|Npm') },
{ value: 'PYPI', text: s__('PackageRegistry|PyPI') },
{ value: 'MAVEN', text: s__('PackageRegistry|Maven') },
];
if (this.glFeatures.packagesProtectedPackagesConan) {
packageTypeOptions.push({ value: 'CONAN', text: s__('PackageRegistry|Conan') });
}
if (this.glFeatures.packagesProtectedPackagesMaven) {
packageTypeOptions.push({ value: 'MAVEN', text: s__('PackageRegistry|Maven') });
}
return packageTypeOptions.sort((a, b) => a.text.localeCompare(b.text));
},
minimumAccessLevelForPushOptions() {

View File

@ -10,6 +10,7 @@ import {
GlSprintf,
GlIcon,
} from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { createAlert } from '~/alert';
import { clearDraft } from '~/lib/utils/autosave';
import { isMetaEnterKeyPair } from '~/lib/utils/common_utils';
@ -56,6 +57,7 @@ import {
WORK_ITEM_TYPE_VALUE_MAP,
WORK_ITEM_TYPE_NAME_INCIDENT,
WORK_ITEM_TYPE_NAME_EPIC,
WIDGET_TYPE_CUSTOM_FIELDS,
} from '../constants';
import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql';
import namespaceWorkItemTypesQuery from '../graphql/namespace_work_item_types.query.graphql';
@ -98,7 +100,10 @@ export default {
import('ee_component/work_items/components/work_item_health_status.vue'),
WorkItemColor: () => import('ee_component/work_items/components/work_item_color.vue'),
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemCustomFields: () =>
import('ee_component/work_items/components/work_item_custom_fields.vue'),
},
mixins: [glFeatureFlagMixin()],
inject: ['fullPath', 'groupPath'],
i18n: {
suggestionTitle: s__('WorkItem|Similar items'),
@ -506,6 +511,12 @@ export default {
shouldDatesRollup() {
return this.selectedWorkItemTypeName === WORK_ITEM_TYPE_NAME_EPIC;
},
workItemCustomFields() {
return findWidget(WIDGET_TYPE_CUSTOM_FIELDS, this.workItem)?.customFieldValues ?? null;
},
showWorkItemCustomFields() {
return this.glFeatures.customFieldsFeature && this.workItemCustomFields;
},
},
watch: {
shouldDiscardDraft: {
@ -984,6 +995,16 @@ export default {
:can-update="canUpdate"
@error="$emit('error', $event)"
/>
<work-item-custom-fields
v-if="showWorkItemCustomFields"
:work-item-id="workItemId"
:work-item-type="selectedWorkItemTypeName"
:custom-fields="workItemCustomFields"
:full-path="fullPath"
:is-group="isGroup"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>
<work-item-parent
v-if="showParentAttribute"
class="work-item-attributes-item"

View File

@ -37,6 +37,7 @@ import {
NEW_WORK_ITEM_IID,
WIDGET_TYPE_LINKED_ITEMS,
STATE_CLOSED,
WIDGET_TYPE_CUSTOM_FIELDS,
} from '../constants';
import workItemByIidQuery from './work_item_by_iid.query.graphql';
import workItemByIdQuery from './work_item_by_id.query.graphql';
@ -323,6 +324,7 @@ export const setNewWorkItemCache = async (
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_LINKED_ITEMS,
WIDGET_TYPE_COLOR,
WIDGET_TYPE_CUSTOM_FIELDS,
WIDGET_TYPE_HIERARCHY,
WIDGET_TYPE_TIME_TRACKING,
WIDGET_TYPE_PARTICIPANTS,
@ -516,6 +518,18 @@ export const setNewWorkItemCache = async (
__typename: 'WorkItemWidgetTimeTracking',
});
}
if (widgetName === WIDGET_TYPE_CUSTOM_FIELDS) {
const customFieldsWidgetData = widgetDefinitions.find(
(definition) => definition.type === WIDGET_TYPE_CUSTOM_FIELDS,
);
widgets.push({
type: WIDGET_TYPE_CUSTOM_FIELDS,
customFieldValues: customFieldsWidgetData?.customFieldValues ?? [],
__typename: 'WorkItemWidgetCustomFields',
});
}
}
});

View File

@ -19,6 +19,10 @@ import {
NEW_WORK_ITEM_IID,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_HIERARCHY,
WIDGET_TYPE_CUSTOM_FIELDS,
CUSTOM_FIELDS_TYPE_SINGLE_SELECT,
CUSTOM_FIELDS_TYPE_MULTI_SELECT,
CUSTOM_FIELDS_TYPE_TEXT,
} from '../constants';
import workItemByIidQuery from './work_item_by_iid.query.graphql';
@ -50,6 +54,39 @@ const updateDatesWidget = (draftData, dates) => {
});
};
const updateCustomFieldsWidget = (sourceData, draftData, customField) => {
if (!customField) return;
const widget = findWidget(WIDGET_TYPE_CUSTOM_FIELDS, draftData.workspace.workItem);
if (!widget) return;
const currentValues =
sourceData?.workspace?.workItem?.widgets?.find((w) => w.type === WIDGET_TYPE_CUSTOM_FIELDS)
?.customFieldValues ?? [];
const updatedCustomFieldValues = currentValues.map((field) => {
if (field?.customField?.id === customField.id) {
if (
field.customField.fieldType === CUSTOM_FIELDS_TYPE_SINGLE_SELECT ||
field.customField.fieldType === CUSTOM_FIELDS_TYPE_MULTI_SELECT
) {
return { ...field, selectedOptions: customField.selectedOptions };
}
if (field.customField.fieldType === CUSTOM_FIELDS_TYPE_TEXT) {
return { ...field, value: customField.textValue };
}
return { ...field, value: customField.numberValue };
}
return field;
});
Object.assign(widget, {
customFieldValues: updatedCustomFieldValues,
__typename: 'WorkItemWidgetCustomFields',
});
};
export const updateNewWorkItemCache = (input, cache) => {
const {
healthStatus,
@ -67,6 +104,7 @@ export const updateNewWorkItemCache = (input, cache) => {
weight,
milestone,
parent,
customField,
} = input;
try {
@ -136,6 +174,7 @@ export const updateNewWorkItemCache = (input, cache) => {
});
updateDatesWidget(draftData, rolledUpDates);
updateCustomFieldsWidget(sourceData, draftData, customField);
// We want to allow users to delete a title for an in-progress work item draft
// as we check for the title being valid when submitting the form

View File

@ -82,6 +82,19 @@ type LocalWorkItemPayload {
errors: [String!]
}
input LocalCustomFieldSelectOptionInput {
id: ID!
value: String
}
input LocalCustomFieldInput {
id: ID!
type: String!
numberValue: Float
textValue: String
selectedOptions: [LocalCustomFieldSelectOptionInput]
}
input LocalUpdateNewWorkItemInput {
fullPath: String!
workItemType: String!
@ -97,6 +110,7 @@ input LocalUpdateNewWorkItemInput {
rolledUpDates: [LocalRolledUpDatesInput]
crmContacts: [LocalCrmContactsInput]
weight: Int
customField: LocalCustomFieldInput
}
extend type Mutation {

View File

@ -34,7 +34,6 @@ module Projects
def set_feature_flag_packages_protected_packages
push_frontend_feature_flag(:packages_protected_packages_conan, project)
push_frontend_feature_flag(:packages_protected_packages_maven, project)
push_frontend_feature_flag(:packages_protected_packages_delete, project)
end

View File

@ -15,9 +15,7 @@ module Types
value 'MAVEN',
value: 'maven',
experiment: { milestone: '17.9' },
description: 'Packages of the Maven format. ' \
'Available only when feature flag `packages_protected_packages_maven` is enabled.'
description: 'Packages of the Maven format.'
value 'NPM',
value: 'npm',

View File

@ -1637,7 +1637,7 @@ class MergeRequest < ApplicationRecord
visible_notes = user.can?(:read_internal_note, project) ? notes : notes.not_internal
messages = [title, description, *visible_notes.pluck(:note)]
messages += commits.map(&:safe_message) if merge_request_diff.persisted?
messages += commits(load_from_gitaly: Feature.enabled?(:more_commits_from_gitaly, target_project)).map(&:safe_message) if merge_request_diff.persisted?
ext = Gitlab::ReferenceExtractor.new(project, user)
ext.analyze(messages.join("\n"))

View File

@ -4,7 +4,7 @@ module Packages
module Maven
class CreatePackageService < ::Packages::CreatePackageService
def execute
return ERROR_RESPONSE_PACKAGE_PROTECTED if package_protected?
return ERROR_RESPONSE_PACKAGE_PROTECTED if package_protected?(package_name: params[:name], package_type: :maven)
app_group, _, app_name = params[:name].rpartition('/')
app_group.tr!('/', '.')
@ -24,14 +24,6 @@ module Packages
ServiceResponse.error(message: e.message, reason: reason)
end
private
def package_protected?
return false if Feature.disabled?(:packages_protected_packages_maven, project)
super(package_name: params[:name], package_type: :maven)
end
end
end
end

View File

@ -8,7 +8,7 @@
class FlushCounterIncrementsWorker
include ApplicationWorker
data_consistency :always
data_consistency :delayed, feature_flag: :load_balancing_for_flush_counter_increments_worker
sidekiq_options retry: 3
loggable_arguments 0, 2

View File

@ -4,7 +4,7 @@ module WebHooks
class LogExecutionWorker
include ApplicationWorker
data_consistency :always
data_consistency :delayed, feature_flag: :load_balancing_for_web_hooks_log_execution_worker
feature_category :webhooks
urgency :low
sidekiq_options retry: 3

View File

@ -1,9 +0,0 @@
---
name: packages_protected_packages_maven
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323969
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147055
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/497082
milestone: '17.9'
group: group::package registry
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: load_balancing_for_flush_counter_increments_worker
feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/data-access/durability/team/-/issues/121
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186079
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/527277
milestone: '17.11'
group: group::durability
type: worker
default_enabled: false

View File

@ -0,0 +1,9 @@
---
name: load_balancing_for_web_hooks_log_execution_worker
feature_issue_url: https://gitlab.com/gitlab-com/gl-infra/data-access/durability/team/-/issues/121
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/186079
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/527276
milestone: '17.11'
group: group::durability
type: worker
default_enabled: false

View File

@ -64,9 +64,9 @@ The following table lists the GitLab Duo features, and whether they are availabl
| [Merge Commit Message Generation](../../user/project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
| [Summarize New Merge Request](../../user/project/merge_requests/duo_in_merge_requests.md#generate-a-description-by-summarizing-code-changes) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
| [Vulnerability Explanation](../../user/application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
| [Vulnerability Resolution](../../user/application_security/vulnerabilities/_index.md#vulnerability-resolution) | {{< icon name="check-circle-dashed" >}} Beta | GitLab 17.11 and later |
| [Discussion Summary](../../user/discussions/_index.md#summarize-issue-discussions-with-duo-chat) | {{< icon name="dash-circle" >}} No | Not applicable |
| [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli) | {{< icon name="dash-circle" >}} No | Not applicable |
| [Vulnerability Resolution](../../user/application_security/vulnerabilities/_index.md#vulnerability-resolution) | {{< icon name="dash-circle" >}} No | Not applicable |
#### Supported Duo Chat features

View File

@ -41446,6 +41446,7 @@ AI features that can be configured through the Duo self-hosted feature settings.
| <a id="aifeaturesduo_chat_troubleshoot_job"></a>`DUO_CHAT_TROUBLESHOOT_JOB` | Duo chat troubleshoot job feature setting. |
| <a id="aifeaturesduo_chat_write_tests"></a>`DUO_CHAT_WRITE_TESTS` | Duo chat write test feature setting. |
| <a id="aifeaturesgenerate_commit_message"></a>`GENERATE_COMMIT_MESSAGE` | Generate commit message feature setting. |
| <a id="aifeaturesresolve_vulnerability"></a>`RESOLVE_VULNERABILITY` | Resolve vulnerability feature setting. |
| <a id="aifeaturessummarize_new_merge_request"></a>`SUMMARIZE_NEW_MERGE_REQUEST` | Summarize new merge request feature setting. |
### `AiMessageRole`
@ -43938,7 +43939,7 @@ Package type of a package protection rule resource.
| Value | Description |
| ----- | ----------- |
| <a id="packagesprotectionrulepackagetypeconan"></a>`CONAN` {{< icon name="warning-solid" >}} | **Introduced** in GitLab 17.6. **Status**: Experiment. Packages of the Conan format. Available only when feature flag `packages_protected_packages_conan` is enabled. |
| <a id="packagesprotectionrulepackagetypemaven"></a>`MAVEN` {{< icon name="warning-solid" >}} | **Introduced** in GitLab 17.9. **Status**: Experiment. Packages of the Maven format. Available only when feature flag `packages_protected_packages_maven` is enabled. |
| <a id="packagesprotectionrulepackagetypemaven"></a>`MAVEN` | Packages of the Maven format. |
| <a id="packagesprotectionrulepackagetypenpm"></a>`NPM` | Packages of the npm format. |
| <a id="packagesprotectionrulepackagetypepypi"></a>`PYPI` | Packages of the PyPI format. |

View File

@ -13,6 +13,8 @@ Use this guide to understand how different kinds of secrets are stored and manag
Broadly speaking, there are two classes of secrets:
<!-- vale gitlab_base.SubstitutionWarning = NO -->
1. **Application secrets.** The GitLab application uses these to implement a particular feature or function.
An example would be access tokens or private keys to create cryptographic signatures. We store
these secrets in the database in encrypted columns.
@ -20,13 +22,17 @@ Broadly speaking, there are two classes of secrets:
1. **Operational secrets.** Used to read and store other secrets or bootstrap the application. For this reason,
they cannot be stored in the database.
These secrets are stored as [Rails credentials](https://guides.rubyonrails.org/security.html#environmental-security)
in the `config/secrets.yml` file, directly for source installation, or through an installer like Omnibus or Helm (where
actual secrets can be stored in an external secrets container like
[Kubernetes secrets](https://kubernetes.io/docs/concepts/configuration/secret/) or [Vault](https://www.vaultproject.io/)).
in the `config/secrets.yml` file:
- Directly for self-compiled installations.
- Through an installer like Omnibus or Helm (where actual secrets can be stored in an external secrets container like
[Kubernetes secrets](https://kubernetes.io/docs/concepts/configuration/secret/) or [Vault](https://www.vaultproject.io/)).
<!-- vale gitlab_base.SubstitutionWarning = YES -->
## Application secrets
Application secrets should be stored in postgres using `ActiveRecord::Encryption`:
Application secrets should be stored in PostgreSQL using `ActiveRecord::Encryption`:
```ruby
class MyModel < ApplicationRecord

View File

@ -171,7 +171,7 @@ To address the above two scenarios, it is advised to do the following prior to u
1. Pause your runners, or block new jobs from starting by adding the following to your `/etc/gitlab/gitlab.rb`:
```ruby
nginx['custom_gitlab_server_config'] = "location ^~ /api/v4/jobs/request {\n deny all;\n return 503;\n}\n"
nginx['custom_gitlab_server_config'] = "location = /api/v4/jobs/request {\n deny all;\n return 503;\n}\n"
```
And reconfigure GitLab with:

View File

@ -279,7 +279,7 @@ ensure that your proxy server does not alter or remove signed HTTP headers.
## 17.7.0
- Git 2.47.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- Git 2.47.0 and later is required by Gitaly. For self-compiled installations, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- FIPS Linux packages now use the system Libgcrypt, except FIPS Linux packages for AmazonLinux 2. Previous versions of the FIPS Linux packages used the
same Libgcrypt used by the regular Linux packages, which was a bug. For more information, see
[the FIPS documentation](../../development/fips_gitlab.md#system-libgcrypt).
@ -360,7 +360,7 @@ The OpenSSL 3 upgrade has been postponed to GitLab 17.7.0.
database migrations as your existing environments. This isn't necessary if you're restoring from backup into the
new environment as the database restore removes the existing database schema definition and uses the definition
that's stored as part of the backup.
- Git 2.46.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- Git 2.46.0 and later is required by Gitaly. For self-compiled installations, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- S3 object storage uploads in Workhorse are now handled by default using the [AWS SDK v2 for Go](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164597). If you experience issues
with S3 object storage uploads, you can downgrade to v1 of by disabling the `workhorse_use_aws_sdk_v2` [feature flag](../../administration/feature_flags.md#enable-or-disable-the-feature).
- When you upgrade to GitLab 17.4, an OAuth application is generated for the Web IDE.
@ -384,7 +384,7 @@ The OpenSSL 3 upgrade has been postponed to GitLab 17.7.0.
## 17.3.0
- Git 2.45.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
- Git 2.45.0 and later is required by Gitaly. For self-compiled installations, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
### Geo installations 17.3.0

View File

@ -40,7 +40,7 @@ the top of the vulnerability's page.
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM: Anthropic [Claude 3 Haiku](https://docs.anthropic.com/en/docs/about-claude/models#claude-3-a-new-generation-of-ai)
@ -108,7 +108,7 @@ The following data is shared with third-party AI APIs:
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
- LLM for GitLab.com: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)

View File

@ -354,7 +354,7 @@ such as:
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
- LLM for GitLab.com: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)

View File

@ -8,6 +8,7 @@ title: GitLab Duo with Amazon Q
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo with Amazon Q
- Offering: GitLab Self-Managed
- Status: Preview/Beta

View File

@ -8,6 +8,7 @@ title: Set up GitLab Duo with Amazon Q
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo with Amazon Q
- Offering: GitLab Self-Managed
- Status: Preview/Beta

View File

@ -116,22 +116,22 @@ To improve your security, try these features:
| Feature | Tier | Add-on | Offering | Status |
| ------- | ---- | ------ | -------- | ------ |
| [GitLab Duo Chat](../gitlab_duo_chat/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [GitLab Duo Chat](../gitlab_duo_chat/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [GitLab Duo Self-Hosted](../../administration/gitlab_duo_self_hosted/_index.md) | Ultimate | GitLab Duo Enterprise | GitLab Self-Managed | General availability |
| [GitLab Duo Workflow](../duo_workflow/_index.md) | Ultimate | - | GitLab.com | Private beta |
| [Issue Description Generation](../project/issues/managing_issues.md#populate-an-issue-with-issue-description-generation) | Ultimate | GitLab Duo Enterprise | GitLab.com | Experiment |
| [Discussion Summary](../discussions/_index.md#summarize-issue-discussions-with-duo-chat) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Suggestions](../project/repository/code_suggestions/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Explanation](../project/repository/code_explain.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Discussion Summary](../discussions/_index.md#summarize-issue-discussions-with-duo-chat) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Suggestions](../project/repository/code_suggestions/_index.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Code Explanation](../project/repository/code_explain.md) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide) | Premium, Ultimate | GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/_index.md#gitlab-duo-for-the-cli) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Merge Request Summary](../project/merge_requests/duo_in_merge_requests.md#generate-a-description-by-summarizing-code-changes) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed | Beta |
| [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | Beta |
| [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed | Experiment |
| [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Root Cause Analysis](../gitlab_duo_chat/examples.md#troubleshoot-failed-cicd-jobs-with-root-cause-analysis) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Explanation](../application_security/vulnerabilities/_index.md#explaining-a-vulnerability) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [Vulnerability Resolution](../application_security/vulnerabilities/_index.md#vulnerability-resolution) | Ultimate | GitLab Duo Enterprise, GitLab Duo with Amazon Q | GitLab.com, GitLab Self-Managed, GitLab Dedicated | General availability |
| [AI Impact Dashboard](../analytics/ai_impact_analytics.md) | Ultimate | GitLab Duo Enterprise | GitLab.com, GitLab Self-Managed | General availability |

View File

@ -8,7 +8,7 @@ title: GitLab Duo Chat
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLMs: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet), Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2), Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet), Anthropic [Claude 3.5 Haiku](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-haiku), and [Vertex AI Search](https://cloud.google.com/enterprise-search). The LLM depends on the question asked.

View File

@ -220,7 +220,7 @@ You can ask about a specific GitLab pipeline job. For example:
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: GitLab UI, Web IDE, VS Code, JetBrains IDEs
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2)
@ -396,7 +396,7 @@ You cannot use [Quick Chat](_index.md#in-gitlab-duo-quick-chat-in-the-editor-vie
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: Web IDE, VS Code, JetBrains IDEs
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2)
@ -432,7 +432,7 @@ You can include additional instructions to be considered. For example:
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: Web IDE, VS Code, JetBrains IDEs
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2)
@ -466,7 +466,7 @@ You can include additional instructions to be considered. For example:
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: Web IDE, VS Code, JetBrains IDEs
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2)
@ -542,7 +542,7 @@ Alternatively, you can use GitLab Duo Root Cause Analysis to [troubleshoot faile
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: GitLab UI
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet V2](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet-v2)

View File

@ -18,6 +18,8 @@ title: Protected packages
- The protection rule setting **Push protected up to access level** [renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/416382) to **Minimum access level for push** in GitLab 17.1
- [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/472655) in GitLab 17.5.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/472655) in GitLab 17.6. Feature flag `packages_protected_packages` removed.
- Maven protected packages [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/323969) in GitLab 17.9 [with a flag](../../../administration/feature_flags.md) named `packages_protected_packages_maven`. Disabled by default. This feature is an [experiment](../../../policy/development_stages_support.md).
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/497082) in GitLab 17.11. Feature flag `packages_protected_packages_maven` removed.
{{< /history >}}
@ -25,7 +27,7 @@ By default, any user with at least the Developer role can create,
edit, and delete packages. Add a package protection rule to restrict
which users can make changes to your packages.
GitLab supports only push protection for npm packages, but [epic 5574](https://gitlab.com/groups/gitlab-org/-/epics/5574) proposes to add additional features and package formats.
GitLab supports only push protection for npm, pypi and maven packages, but [epic 5574](https://gitlab.com/groups/gitlab-org/-/epics/5574) proposes to add additional features and package formats.
When a package is protected, the default behavior enforces these restrictions on the package:

View File

@ -247,7 +247,7 @@ To change how a merge request shows changed lines:
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
- LLM for GitLab.com: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)

View File

@ -8,7 +8,7 @@ title: Explain code in a file
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise. GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM for GitLab Self-Managed, GitLab Dedicated: Anthropic [Claude 3.5 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-5-sonnet)
- LLM for GitLab.com: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)

View File

@ -9,7 +9,7 @@ title: Code Suggestions
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLMs: For code completion, Fireworks AI-hosted [`Qwen2.5 7B`](https://fireworks.ai/models/fireworks/qwen2p5-coder-7b) and Vertex AI Codey [`code-gecko`](https://console.cloud.google.com/vertex-ai/publishers/google/model-garden/code-gecko). For code generation, Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet).

View File

@ -9,7 +9,7 @@ title: Supported extensions and languages
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -9,7 +9,7 @@ title: Troubleshooting Code Suggestions
{{< details >}}
- Tier: Premium, Ultimate
- Add-on: GitLab Duo Pro or Enterprise
- Add-on: GitLab Duo Pro or Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
{{< /details >}}

View File

@ -85,8 +85,8 @@ Class methods required:
Instance methods required:
- `init`: reads from `serialized_args`
- `as_indexed_json`: a hash containing the data representation of the object
- `operation`: determines the operation which can be one of `index`, `upsert` or `delete`
- `as_indexed_json` or `as_indexed_jsons`: a hash or array of hashes containing the data representation of the object
- `operation`: determines the operation which can be one of `upsert` or `delete`
- `identifier`: unique identifier
Example for a reference reading from a database relation, with preloading and bulk embedding generation:
@ -194,7 +194,7 @@ module Ai
blob.data ? :upsert : :delete
end
def as_indexed_json
def as_indexed_jsons
{
project_id: project_id,
embeddings: embedding

View File

@ -34,7 +34,7 @@ module ActiveContext
'queue' => queue,
'message' => 'bulk_indexing_start',
'meta.indexing.redis_set' => set_key,
'meta.indexing.records_count' => specs.count,
'meta.indexing.refs_count' => specs.count,
'meta.indexing.first_score' => first_score,
'meta.indexing.last_score' => last_score
)
@ -73,7 +73,7 @@ module ActiveContext
'class' => self.class.name,
'message' => 'bulk_indexing_end',
'meta.indexing.redis_set' => set_key,
'meta.indexing.records_count' => count,
'meta.indexing.refs_count' => count,
'meta.indexing.first_score' => first_score,
'meta.indexing.last_score' => last_score,
'meta.indexing.failures_count' => @failures.count,

View File

@ -33,7 +33,8 @@ module ActiveContext
def create_partition(name, fields)
body = {
mappings: {
properties: build_field_mappings(fields)
dynamic: 'strict',
properties: mappings(fields)
},
settings: settings(fields)
}
@ -51,6 +52,13 @@ module ActiveContext
raw_client.indices.update_aliases(body: { actions: actions })
end
def mappings(fields)
build_field_mappings(fields).merge(
ref_id: { type: 'keyword' },
ref_version: { type: 'long' }
)
end
def build_field_mappings(fields)
fields.each_with_object({}) do |field, mappings|
mappings[field.name] = case field

View File

@ -10,80 +10,120 @@ module ActiveContext
DEFAULT_MAX_BULK_SIZE = 10.megabytes
attr_reader :operations, :bulk_size
attr_reader :index_operations, :bulk_size
def initialize(...)
super
@operations = []
@index_operations = []
@bulk_size = 0
end
def add_ref(ref)
operation = build_operation(ref)
@refs << ref
@operations << operation
@bulk_size += calculate_operation_size(operation)
build_index_operations(ref)
bulk_size >= bulk_threshold
end
def empty?
operations.empty?
refs.empty?
end
# Executes upsert operations in one bulk request
# Create a single delete_by_query request for processing deletes
def bulk
client.bulk(body: operations.flatten)
end
results = []
def process_bulk_errors(result)
return [] unless result['errors']
results << client.bulk(body: index_operations.flatten, refresh: true) unless index_operations.empty?
failed_refs = []
result['items'].each_with_index do |item, index|
op = item['index'] || item['update'] || item['delete']
next unless op.nil? || op['error']
ref = refs[index]
logger.warn(
'message' => 'indexing_failed',
'meta.indexing.error' => op&.dig('error') || 'Operation was nil',
'meta.indexing.status' => op&.dig('status'),
'meta.indexing.operation_type' => item.each_key.first,
'meta.indexing.ref' => ref.serialize,
'meta.indexing.identifier' => ref.identifier
build_delete_operations.each do |op|
results << client.delete_by_query(
index: op[:index],
body: op[:body]
)
failed_refs << ref
end
failed_refs
results
end
def process_bulk_errors(results)
failed_refs_set = Set.new
results.each do |result|
next unless result['errors']
result['items'].each do |item|
op = item['index'] || item['update'] || item['delete']
next unless op.nil? || op['error']
ref = refs.find { |ref| ref.identifier == extract_identifier(op['_id']) }
logger.warn(
'message' => 'indexing_failed',
'meta.indexing.error' => op&.dig('error') || 'Operation was nil',
'meta.indexing.status' => op&.dig('status'),
'meta.indexing.operation_type' => item.each_key.first,
'meta.indexing.ref' => ref&.serialize,
'meta.indexing.identifier' => ref&.identifier
)
failed_refs_set.add(ref) if ref
end
end
failed_refs_set.to_a
end
def reset
super
@operations = []
@index_operations = []
@bulk_size = 0
end
private
def build_operation(ref)
case ref.operation.to_sym
when :index, :upsert
[
{ update: { _index: ref.partition, _id: ref.identifier, routing: ref.routing }.compact },
{ doc: ref.as_indexed_json, doc_as_upsert: true }
]
when :delete
[{ delete: { _index: ref.partition, _id: ref.identifier, routing: ref.routing }.compact }]
else
raise StandardError, "Operation #{ref.operation} is not supported"
# Builds an upsert operation for every ref where operation is :upsert
# These operations will be processed in bulk
def build_index_operations(ref)
return unless ref.operation.to_sym == :upsert
ref.jsons.map.with_index do |hash, index|
add_index_operation([
{ update: { _index: ref.partition, _id: unique_identifier(ref, index), routing: ref.routing }.compact },
{ doc: hash, doc_as_upsert: true }
])
end
end
# Builds up a bool query containing multiple shoulds:
# A single terms query containing ids of refs where operation is :delete
# A bool query with a `filter` for the `ref_id` and `must_not` for the `ref_version` for :upsert refs
# This ensures we only delete old versions of the document
def build_delete_operations
delete_operations = []
refs.group_by(&:partition).each do |partition, partition_refs|
shoulds = []
ref_ids_to_delete = []
partition_refs.each do |ref|
case ref.operation.to_sym
when :upsert
shoulds << delete_with_version_query(ref)
when :delete
ref_ids_to_delete << ref.identifier
else
raise StandardError, "Operation #{ref.operation} is not supported"
end
end
delete_operations << { index: partition, body: build_delete_query(shoulds, ref_ids_to_delete) }
end
delete_operations
end
def calculate_operation_size(operation)
operation.to_json.bytesize + 2 # Account for newlines
end
@ -95,6 +135,37 @@ module ActiveContext
def logger
@logger ||= ActiveContext::Config.logger
end
def add_index_operation(op)
@index_operations << op
@bulk_size += calculate_operation_size(op)
end
def delete_without_version_query(ref_ids)
{ terms: { ref_id: ref_ids } }
end
def delete_with_version_query(ref)
{
bool: {
filter: { term: { ref_id: ref.identifier } },
must_not: { term: { ref_version: ref.ref_version } }
}
}
end
def build_delete_query(shoulds, ref_ids_to_delete)
shoulds << delete_without_version_query(ref_ids_to_delete) if ref_ids_to_delete.any?
{
query: {
bool: {
should: shoulds,
minimum_should_match: 1
}
}
}
end
end
end
end

View File

@ -53,6 +53,14 @@ module ActiveContext
@refs = []
# also reset anything that builds up from the refs array
end
def unique_identifier(ref, index)
"#{ref.identifier}:#{index}"
end
def extract_identifier(string)
string.split(':').first
end
end
end
end

View File

@ -6,7 +6,7 @@ module ActiveContext
class Client
include ActiveContext::Databases::Concerns::Client
delegate :bulk, to: :client
delegate :bulk, :delete_by_query, to: :client
OPEN_TIMEOUT = 5
NO_RETRY = 0

View File

@ -9,7 +9,7 @@ module ActiveContext
class Client
include ActiveContext::Databases::Concerns::Client
delegate :bulk, to: :client
delegate :bulk, :delete_by_query, to: :client
OPEN_TIMEOUT = 5
NO_RETRY = 0

View File

@ -166,7 +166,17 @@ module ActiveContext
end
end
when :delete
model.where(id: data).delete_all
prepare_delete_data(data).each do |group|
ref_ids = group[:ref_ids]
ref_version = group[:ref_version]
next if ref_ids.empty?
query = model.where(ref_id: ref_ids)
query = query.where.not(ref_version: ref_version) if ref_version.present?
query.delete_all
end
end
[]
@ -185,6 +195,16 @@ module ActiveContext
}
end
end
def prepare_delete_data(data)
data_by_version = data.group_by { |record| record[:ref_version] }
data_by_version.map do |ref_version, records|
{
ref_version: ref_version,
ref_ids: records.pluck(:ref_id)
}
end
end
end
end
end

View File

@ -48,8 +48,12 @@ module ActiveContext
add_column_from_field(table, field)
end
# Add id column
# Add id columns
table.string :id, null: false
table.string :ref_id, null: false
# Add ref_version column
table.bigint :ref_version, null: false
# Add variable width columns last
variable_columns.each do |field|

View File

@ -15,7 +15,7 @@ module ActiveContext
def add_ref(ref)
@refs << ref
@operations << build_operation(ref)
build_operation(ref)
refs.size >= BATCH_SIZE
end
@ -43,20 +43,33 @@ module ActiveContext
def build_operation(ref)
case ref.operation.to_sym
when :upsert
{ "#{ref.partition_name}": { upsert: build_indexed_json(ref) }, ref: ref }
ref.jsons.each.with_index do |hash, index|
@operations << { "#{ref.partition_name}": { upsert: build_indexed_json(hash, ref, index) }, ref: ref }
end
@operations << build_delete_operation(ref: ref, include_ref_version: true)
when :delete
{ "#{ref.partition_name}": { delete: ref.identifier }, ref: ref }
@operations << build_delete_operation(ref: ref)
else
raise StandardError, "Operation #{ref.operation} is not supported"
end
end
def build_indexed_json(ref)
ref.as_indexed_json
.merge(partition_id: ref.partition_number)
def build_indexed_json(hash, ref, index)
hash
.merge(
partition_id: ref.partition_number,
id: unique_identifier(ref, index)
)
.transform_values { |value| convert_pg_array(value) }
end
def build_delete_operation(ref:, include_ref_version: false)
hash = { ref_id: ref.identifier }
hash[:ref_version] = ref.ref_version if include_ref_version
{ "#{ref.partition_name}": { delete: hash }, ref: ref }
end
def convert_pg_array(value)
value.is_a?(Array) ? "[#{value.join(',')}]" : value
end

View File

@ -35,13 +35,14 @@ module ActiveContext
end
end
attr_reader :collection_id, :collection, :routing, :serialized_args
attr_reader :collection_id, :collection, :routing, :serialized_args, :ref_version
def initialize(collection_id:, routing:, args: [])
@collection_id = collection_id.to_i
@collection = ActiveContext::CollectionCache.fetch(@collection_id)
@routing = routing
@serialized_args = Array(args)
@ref_version = Time.now.to_i
init
end
@ -61,8 +62,19 @@ module ActiveContext
raise NotImplementedError
end
def as_indexed_json
raise NotImplementedError
def jsons
as_indexed_jsons.map do |json|
json.merge(
ref_id: identifier,
ref_version: ref_version
)
end
end
def as_indexed_jsons
return Array.wrap(as_indexed_json) if respond_to?(:as_indexed_json)
raise NotImplementedError, "#{self.class} must implement either :as_indexed_json or :as_indexed_jsons"
end
def operation

View File

@ -4,22 +4,28 @@ RSpec.describe ActiveContext::BulkProcessor do
let(:connection) { double('Connection') }
let(:adapter) { ActiveContext::Databases::Elasticsearch::Adapter.new(connection, options: { url: 'http://localhost:9200' }) }
let(:logger) { instance_double(Logger) }
let(:ref) { double }
let(:reference_class) { Test::References::MockWithDatabaseRecord }
let(:ref) { reference_class.new(collection_id: collection_id, routing: partition, args: 1) }
let(:mock_collection) { double(name: collection_name, partition_for: partition) }
let(:mock_object) { double(id: object_id) }
let(:mock_relation) { double(find_by: mock_object) }
let(:mock_connection) { double(id: connection_id) }
let(:connection_id) { 3 }
let(:partition) { 2 }
let(:collection_id) { 1 }
let(:object_id) { 5 }
let(:collection_name) { 'mock_collection' }
before do
allow(ActiveContext).to receive(:adapter).and_return(adapter)
allow(ActiveContext::Config).to receive(:logger).and_return(logger)
allow(ActiveContext::CollectionCache).to receive(:fetch).and_return(mock_collection)
allow(ActiveContext::Logger).to receive(:exception).and_return(nil)
allow(reference_class).to receive(:model_klass).and_return(mock_relation)
allow(logger).to receive(:info)
allow(logger).to receive(:error)
allow(ref).to receive_messages(
operation: :index,
id: 1,
as_indexed_json: { title: 'Test Issue' },
partition_name: 'issues',
partition: 'issues_0',
identifier: '1',
routing: 'group_1'
)
end
describe '#initialize' do
@ -66,7 +72,7 @@ RSpec.describe ActiveContext::BulkProcessor do
end
it 'processes bulk and logs info' do
allow(adapter).to receive(:bulk).and_return({ 'items' => [] })
allow(adapter).to receive(:bulk).and_return([{ 'items' => [] }])
expect(logger).to receive(:info).with(
'message' => 'bulk_submitted',
@ -78,7 +84,7 @@ RSpec.describe ActiveContext::BulkProcessor do
end
it 'resets the adapter after processing' do
allow(adapter).to receive(:bulk).and_return({ 'items' => [] })
allow(adapter).to receive(:bulk).and_return([{ 'items' => [] }])
expect(adapter).to receive(:reset)
processor.send(:send_bulk)
@ -94,7 +100,7 @@ RSpec.describe ActiveContext::BulkProcessor do
context 'when bulk processing succeeds' do
it 'returns empty array' do
allow(adapter).to receive(:bulk).and_return({ 'items' => [] })
allow(adapter).to receive(:bulk).and_return([{ 'items' => [] }])
expect(processor.send(:try_send_bulk)).to eq([])
end
end

View File

@ -1,122 +1,8 @@
# frozen_string_literal: true
RSpec.describe ActiveContext::Databases::Elasticsearch::Indexer do
let(:es_client) { instance_double(Elasticsearch::Client) }
let(:logger) { instance_double(Logger, warn: nil) }
let(:options) { {} }
let(:indexer) { described_class.new(options, es_client) }
let(:ref) { double }
let(:client) { instance_double(Elasticsearch::Client) }
let(:indexer) { described_class.new(options, client) }
before do
allow(ActiveContext::Config).to receive(:logger).and_return(logger)
allow(ref).to receive_messages(
operation: :index,
id: 1,
as_indexed_json: { title: 'Test Issue' },
partition_name: 'issues',
identifier: '1',
partition: 'issues_0',
routing: 'group_1',
serialize: 'issue 1 group_1'
)
end
describe '#initialize' do
it 'initializes with empty operations and zero bulk size' do
expect(indexer.operations).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
describe '#add_ref' do
it 'adds the ref and returns true when bulk threshold is reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1)
expect(indexer.add_ref(ref)).to be true
expect(indexer.operations).not_to be_empty
end
it 'adds the ref and returns false when bulk threshold is not reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1000000)
expect(indexer.add_ref(ref)).to be false
expect(indexer.operations).not_to be_empty
end
it 'raises an error for unsupported operations' do
allow(ref).to receive(:operation).and_return(:unsupported)
expect { indexer.add_ref(ref) }.to raise_error(StandardError, /Operation unsupported is not supported/)
end
end
describe '#empty?' do
it 'returns true when there are no operations' do
expect(indexer).to be_empty
end
it 'returns false when there are operations' do
indexer.instance_variable_set(:@operations, [{}])
expect(indexer).not_to be_empty
end
end
describe '#bulk' do
before do
indexer.instance_variable_set(:@operations, [{ index: {} }])
end
it 'calls bulk on the client with flattened operations' do
expect(es_client).to receive(:bulk).with(body: [{ index: {} }])
indexer.bulk
end
end
describe '#process_bulk_errors' do
before do
indexer.instance_variable_set(:@refs, [ref])
end
context 'when there are no errors' do
it 'returns an empty array' do
result = { 'errors' => false }
expect(indexer.process_bulk_errors(result)).to be_empty
end
end
context 'when there are errors' do
let(:result) do
{
'errors' => true,
'items' => [
{ 'index' => { 'error' => 'Error message', 'status' => 400 } }
]
}
end
it 'logs warnings and returns failed refs' do
expect(logger).to receive(:warn).with(
'message' => 'indexing_failed',
'meta.indexing.error' => 'Error message',
'meta.indexing.status' => 400,
'meta.indexing.operation_type' => 'index',
'meta.indexing.ref' => 'issue 1 group_1',
'meta.indexing.identifier' => '1'
)
failed_refs = indexer.process_bulk_errors(result)
expect(failed_refs).to eq([ref])
end
end
end
describe '#reset' do
before do
indexer.instance_variable_set(:@operations, [{}])
indexer.instance_variable_set(:@bulk_size, 100)
end
it 'resets operations and bulk size' do
indexer.reset
expect(indexer.operations).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
it_behaves_like 'an elastic indexer'
end

View File

@ -1,122 +1,8 @@
# frozen_string_literal: true
RSpec.describe ActiveContext::Databases::Opensearch::Indexer do
let(:opensearch_client) { instance_double(OpenSearch::Client) }
let(:logger) { instance_double(Logger, warn: nil) }
let(:options) { {} }
let(:indexer) { described_class.new(options, opensearch_client) }
let(:ref) { double }
let(:client) { instance_double(OpenSearch::Client) }
let(:indexer) { described_class.new(options, client) }
before do
allow(ActiveContext::Config).to receive(:logger).and_return(logger)
allow(ref).to receive_messages(
operation: :index,
id: 1,
as_indexed_json: { title: 'Test Issue' },
partition_name: 'issues',
identifier: '1',
partition: 'issues_0',
routing: 'group_1',
serialize: 'issue 1 group_1'
)
end
describe '#initialize' do
it 'initializes with empty operations and zero bulk size' do
expect(indexer.operations).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
describe '#add_ref' do
it 'adds the ref and returns true when bulk threshold is reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1)
expect(indexer.add_ref(ref)).to be true
expect(indexer.operations).not_to be_empty
end
it 'adds the ref and returns false when bulk threshold is not reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1000000)
expect(indexer.add_ref(ref)).to be false
expect(indexer.operations).not_to be_empty
end
it 'raises an error for unsupported operations' do
allow(ref).to receive(:operation).and_return(:unsupported)
expect { indexer.add_ref(ref) }.to raise_error(StandardError, /Operation unsupported is not supported/)
end
end
describe '#empty?' do
it 'returns true when there are no operations' do
expect(indexer).to be_empty
end
it 'returns false when there are operations' do
indexer.instance_variable_set(:@operations, [{}])
expect(indexer).not_to be_empty
end
end
describe '#bulk' do
before do
indexer.instance_variable_set(:@operations, [{ index: {} }])
end
it 'calls bulk on the client with flattened operations' do
expect(opensearch_client).to receive(:bulk).with(body: [{ index: {} }])
indexer.bulk
end
end
describe '#process_bulk_errors' do
before do
indexer.instance_variable_set(:@refs, [ref])
end
context 'when there are no errors' do
it 'returns an empty array' do
result = { 'errors' => false }
expect(indexer.process_bulk_errors(result)).to be_empty
end
end
context 'when there are errors' do
let(:result) do
{
'errors' => true,
'items' => [
{ 'index' => { 'error' => 'Error message', 'status' => 400 } }
]
}
end
it 'logs warnings and returns failed refs' do
expect(logger).to receive(:warn).with(
'message' => 'indexing_failed',
'meta.indexing.error' => 'Error message',
'meta.indexing.status' => 400,
'meta.indexing.operation_type' => 'index',
'meta.indexing.ref' => 'issue 1 group_1',
'meta.indexing.identifier' => '1'
)
failed_refs = indexer.process_bulk_errors(result)
expect(failed_refs).to eq([ref])
end
end
end
describe '#reset' do
before do
indexer.instance_variable_set(:@operations, [{}])
indexer.instance_variable_set(:@bulk_size, 100)
end
it 'resets operations and bulk size' do
indexer.reset
expect(indexer.operations).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
it_behaves_like 'an elastic indexer'
end

View File

@ -323,17 +323,17 @@ RSpec.describe ActiveContext::Databases::Postgresql::Client do
let(:collection_name) { 'test_collection' }
let(:operations) do
[
{ collection_name => { delete: 1 } }
{ collection_name => { delete: { ref_id: 1 } } }
]
end
before do
allow(model_class).to receive(:where).with(id: [1]).and_return(model_class)
allow(model_class).to receive(:where).with(ref_id: [1]).and_return(model_class)
allow(model_class).to receive(:delete_all).and_return(1)
end
it 'processes delete operations with the model' do
expect(model_class).to receive(:where).with(id: [1])
expect(model_class).to receive(:where).with(ref_id: [1])
expect(model_class).to receive(:delete_all)
result = client.bulk_process(operations)
@ -654,15 +654,15 @@ RSpec.describe ActiveContext::Databases::Postgresql::Client do
context 'with delete operation' do
let(:operation_type) { :delete }
let(:operation_data) { 1 }
let(:operation_data) { { ref_id: 1 } }
before do
allow(model).to receive(:where).with(id: [operation_data]).and_return(model)
allow(model).to receive(:where).with(ref_id: [1]).and_return(model)
allow(model).to receive(:delete_all).and_return(1)
end
it 'processes delete operations successfully' do
expect(model).to receive(:where).with(id: [operation_data])
expect(model).to receive(:where).with(ref_id: [1])
expect(model).to receive(:delete_all)
result = client.send(:perform_bulk_operation, operation_type, model, collection_name, operations)

View File

@ -21,10 +21,16 @@ module Test
:upsert
end
def as_indexed_json
{
id: identifier
}
def as_indexed_jsons
[{ id: identifier }]
end
def partition_name
'test'
end
def partition
"#{partition_name}_0"
end
end
end

View File

@ -3,6 +3,14 @@
module Test
module References
class MockWithDatabaseRecord < Mock
def self.model_klass
Class.new do
def self.find_by(id:)
{ id: id }
end
end
end
def model_klass
self.class.model_klass
end

View File

@ -0,0 +1,257 @@
# frozen_string_literal: true
RSpec.shared_examples 'an elastic indexer' do
let(:logger) { instance_double(Logger, warn: nil) }
let(:options) { {} }
let(:ref) { double }
before do
allow(ActiveContext::Config).to receive(:logger).and_return(logger)
allow(ref).to receive_messages(
operation: :upsert,
id: 1,
partition_name: 'issues',
identifier: '1',
partition: 'issues_0',
routing: 'group_1',
serialize: 'issue 1 group_1',
jsons: [{ title: 'Test Issue' }],
ref_version: 123456
)
end
describe '#initialize' do
it 'initializes with empty operations and zero bulk size' do
expect(indexer.index_operations).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
describe '#add_ref' do
it 'adds the ref and returns true when bulk threshold is reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1)
expect(indexer.add_ref(ref)).to be true
expect(indexer.index_operations).not_to be_empty
end
it 'adds the ref and returns false when bulk threshold is not reached' do
allow(indexer).to receive(:bulk_threshold).and_return(1000000)
expect(indexer.add_ref(ref)).to be false
expect(indexer.instance_variable_get(:@refs)).to include(ref)
end
it 'raises an error for unsupported operations' do
allow(ref).to receive(:operation).and_return(:unsupported)
indexer.instance_variable_set(:@refs, [ref])
expect { indexer.send(:build_delete_operations) }
.to raise_error(StandardError, /Operation unsupported is not supported/)
end
end
describe '#empty?' do
it 'returns true when there are no operations' do
expect(indexer).to be_empty
end
it 'returns false when there are operations' do
indexer.instance_variable_set(:@refs, [ref])
expect(indexer).not_to be_empty
end
end
describe '#bulk' do
context 'when only index operations are present' do
before do
indexer.instance_variable_set(:@index_operations, [{ index: {} }])
indexer.instance_variable_set(:@refs, [])
end
it 'calls bulk on the client with flattened operations' do
expect(client).to receive(:bulk).with(body: [{ index: {} }], refresh: true)
indexer.bulk
end
end
context 'when delete operations are present' do
let(:delete_ref) { double }
before do
indexer.instance_variable_set(:@index_operations, [])
allow(delete_ref).to receive_messages(
operation: :delete,
identifier: '1',
partition: 'issues_0'
)
indexer.instance_variable_set(:@refs, [delete_ref])
end
it 'calls delete_by_query on the client with the correct parameters' do
expect(client).to receive(:delete_by_query).with(
hash_including(
index: 'issues_0',
body: hash_including(
query: hash_including(
bool: hash_including(
should: array_including(
hash_including(terms: hash_including(ref_id: ['1']))
),
minimum_should_match: 1
)
)
)
)
)
indexer.bulk
end
end
context 'when both index and delete operations are present' do
let(:delete_ref) { double }
before do
indexer.instance_variable_set(:@index_operations, [{ index: {} }])
allow(delete_ref).to receive_messages(
operation: :delete,
identifier: '1',
partition: 'issues_0'
)
indexer.instance_variable_set(:@refs, [delete_ref])
end
it 'calls both bulk and delete_by_query on the client' do
expect(client).to receive(:bulk).with(body: [{ index: {} }], refresh: true)
expect(client).to receive(:delete_by_query).with(
hash_including(
index: 'issues_0',
body: hash_including(
query: hash_including(
bool: hash_including(
should: array_including(
hash_including(terms: hash_including(ref_id: ['1']))
),
minimum_should_match: 1
)
)
)
)
)
indexer.bulk
end
end
end
describe '#process_bulk_errors' do
before do
indexer.instance_variable_set(:@refs, [ref])
end
context 'when there are no errors' do
it 'returns an empty array' do
result = [{ 'errors' => false }]
expect(indexer.process_bulk_errors(result)).to be_empty
end
end
context 'when there are errors' do
let(:result) do
[{
'errors' => true,
'items' => [
{ 'index' => { '_id' => '1:0', 'error' => 'Error message', 'status' => 400 } }
]
}]
end
it 'logs warnings and returns failed refs' do
allow(indexer).to receive(:extract_identifier).with(nil).and_return(nil)
allow(indexer).to receive(:extract_identifier).with('1:0').and_return('1')
expect(logger).to receive(:warn).with(
'message' => 'indexing_failed',
'meta.indexing.error' => 'Error message',
'meta.indexing.status' => 400,
'meta.indexing.operation_type' => 'index',
'meta.indexing.ref' => 'issue 1 group_1',
'meta.indexing.identifier' => '1'
)
failed_refs = indexer.process_bulk_errors(result)
expect(failed_refs).to eq([ref])
end
end
end
describe '#reset' do
before do
indexer.instance_variable_set(:@index_operations, [{}])
indexer.instance_variable_set(:@refs, [ref])
indexer.instance_variable_set(:@bulk_size, 100)
end
it 'resets operations and bulk size' do
indexer.reset
expect(indexer.index_operations).to be_empty
expect(indexer.instance_variable_get(:@refs)).to be_empty
expect(indexer.bulk_size).to eq(0)
end
end
describe '#build_delete_operations' do
context 'when operation is :upsert' do
before do
allow(ref).to receive(:operation).and_return(:upsert)
indexer.instance_variable_set(:@refs, [ref])
end
it 'creates delete operations with version query' do
delete_ops = indexer.send(:build_delete_operations)
expect(delete_ops.first[:index]).to eq('issues_0')
expect(delete_ops.first[:body][:query][:bool][:should].first[:bool]).to be_present
expect(delete_ops.first[:body][:query][:bool][:minimum_should_match]).to eq(1)
end
end
context 'when operation is :delete' do
let(:delete_ref) { double }
before do
allow(delete_ref).to receive_messages(
operation: :delete,
identifier: '1',
partition: 'issues_0'
)
indexer.instance_variable_set(:@refs, [delete_ref])
end
it 'creates delete operations with terms query' do
delete_ops = indexer.send(:build_delete_operations)
expect(delete_ops.first[:index]).to eq('issues_0')
expect(delete_ops.first[:body][:query][:bool][:should].first[:terms]).to be_present
expect(delete_ops.first[:body][:query][:bool][:minimum_should_match]).to eq(1)
end
end
end
describe '#build_index_operations' do
it 'adds index operations for upsert' do
indexer.instance_variable_set(:@index_operations, [])
indexer.instance_variable_set(:@bulk_size, 0)
allow(ref).to receive(:operation).and_return(:upsert)
indexer.send(:build_index_operations, ref)
expect(indexer.index_operations).not_to be_empty
expect(indexer.bulk_size).to be > 0
end
end
end

View File

@ -242,10 +242,8 @@ module API
put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload!
if Feature.enabled?(:packages_protected_packages_maven, user_project)
package_name = params[:path].rpartition('/').first
protect_package!(package_name, :maven)
end
package_name = params[:path].rpartition('/').first
protect_package!(package_name, :maven)
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE

View File

@ -1,3 +1,7 @@
# This template is for testing cutting-edge features.
# It is not considered stable and might include breaking changes that are planned for the next major release.
# For more information: https://docs.gitlab.com/user/application_security/detect/roll_out_security_scanning/#template-editions
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/development/cicd/templates/
# This specific template is located at:

View File

@ -1,3 +1,7 @@
# This template is for testing cutting-edge features.
# It is not considered stable and might include breaking changes that are planned for the next major release.
# For more information: https://docs.gitlab.com/user/application_security/detect/roll_out_security_scanning/#template-editions
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/development/cicd/templates/
# This specific template is located at:

View File

@ -1,3 +1,7 @@
# This template is for testing cutting-edge features.
# It is not considered stable and might include breaking changes that are planned for the next major release.
# For more information: https://docs.gitlab.com/user/application_security/detect/roll_out_security_scanning/#template-editions
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/iac_scanning/
#
# Configure SAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/).

View File

@ -1,3 +1,7 @@
# This template is for testing cutting-edge features.
# It is not considered stable and might include breaking changes that are planned for the next major release.
# For more information: https://docs.gitlab.com/user/application_security/sast/#stable-vs-latest-sast-templates
#
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
#
# Configure SAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/).

View File

@ -1,3 +1,7 @@
# This template is for testing cutting-edge features.
# It is not considered stable and might include breaking changes that are planned for the next major release.
# For more information: https://docs.gitlab.com/user/application_security/detect/roll_out_security_scanning/#template-editions
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/secret_detection
#
# Configure the scanning tool through the environment variables.

View File

@ -66955,6 +66955,9 @@ msgstr ""
msgid "WorkItemCustomFields|Failed to load custom fields."
msgstr ""
msgid "WorkItemCustomFields|Options could not be loaded for field: %{customFieldName}. Please try again."
msgstr ""
msgid "WorkItemCustomFields|Options could not be loaded for field: %{dropdownLabel}. Please try again."
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -1,288 +1,286 @@
{
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 37.207076614,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 87.91302946399999,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 106.74897681499999,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 106.524588536,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 99.92565242,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 16.319589317,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 99.668106968,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 58.647108560999996,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 62.553768578,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 200.961366816,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 93.299031782,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 90.30839749,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 12.777861528,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 19.691846473,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 15.367861742,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 22.694099294,
"qa/specs/features/api/3_create/merge_request/push_options_spec.rb": 42.754932313,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 0.76019081,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 28.824045455,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 10.793957657,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 14.329634175,
"qa/specs/features/api/3_create/repository/files_spec.rb": 7.03710898,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 10.503566799,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 15.217855771,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 18.914663631,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 10.706025635,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 96.289392366,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 15.150942196,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 53.61972165,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 76.561662825,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 6.288770821,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.8853165,
"qa/specs/features/api/9_tenant_scale/user_inherited_access_spec.rb": 83.69603124600002,
"qa/specs/features/api/9_tenant_scale/users_spec.rb": 5.886840898,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 20.867824391,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 56.876938819,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 38.964384716,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 12.901393894,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 90.797501204,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 3.564938402,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 27.071739976,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 15.627328393,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 40.165915059,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 110.890801518,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 26.258944727,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 35.207811022,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 36.893613149000004,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 30.30578855,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 11.848021995,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 13.381185554,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 71.673357617,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 52.89126454,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_issue_import_spec.rb": 47.05295486,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 69.335723063,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 55.699516126,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_user_contribution_reassignment_spec.rb": 129.866549506,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 22.657133123,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 19.437812084,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 23.46002424,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 21.213680251,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 47.462248957,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 23.190359377,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 25.35169902,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 96.045413587,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 26.053069959,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 44.520782235,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 30.736119828,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 26.933119197,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 39.820321974,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 21.03936907,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 14.710257794,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 104.76434839000001,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 16.274443569,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 30.612303234,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 76.268792724,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 43.012629031,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 14.512877393,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 38.402729613,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 56.591316352999996,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 48.519975719,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 26.487291187,
"qa/specs/features/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 127.484864026,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 46.270185617,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 32.477435558,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 104.69259979899999,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 51.659230202,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 76.296143477,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 35.632093892,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 140.941483608,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 68.137962655,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 61.142612451,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 65.668166985,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 20.597550643,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 20.29884586,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 17.88420823,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 49.311672463,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 40.381127155,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 87.010305845,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 25.380460108,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 16.111231942,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 23.780837922,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 67.684553122,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 63.620997057,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 53.92652669,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 37.048772877,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_file_size_spec.rb": 54.173852355,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 51.322948611,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 24.681057654,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 24.52283251,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 28.413507409,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 34.291079461,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 39.663983822000006,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 30.101478119,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 38.96667861,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 52.105896124,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 52.745480488,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 45.73494453,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 17.936878554,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 13.901061532,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 17.87558427,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 27.653818959,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 44.093374056,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 30.206094125,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 71.85765468700001,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 18.87199177,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 53.059544865,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 71.735155713,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 14.820729826,
"qa/specs/features/browser_ui/3_create/web_ide/settings_sync_web_ide_spec.rb": 173.975187678,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 93.318060577,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 178.270933275,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 106.014626072,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 53.140124308,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 80.708315384,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 252.641044663,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 322.869775152,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 81.585566202,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 189.002493545,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 33.862400457,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 103.601551263,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 27.491900575,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 61.261400953,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 55.167737803,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 153.736479467,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 86.508999336,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 123.151451935,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 84.057822193,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 45.256516682,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 29.090123232,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 17.730218708,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 31.621485767,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 26.059744009,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 15.953068157,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 21.118085708,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 46.694633526,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 70.088950274,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 338.923458313,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 161.297850669,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 57.009161977,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 86.950567213,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 57.258997403,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 280.273624913,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 488.86347062000004,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 333.228560615,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 265.746578356,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 289.779857245,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 294.921764442,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 86.880618931,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 30.683660323,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 151.384157101,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.431704801,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 51.293519028999995,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 74.735893116,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 53.006056884,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 59.754604756000006,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 17.146788374,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 21.699033327,
"qa/specs/features/api/10_govern/group_access_token_spec.rb": 40.691143808,
"qa/specs/features/api/10_govern/project_access_token_spec.rb": 87.194960269,
"qa/specs/features/api/12_systems/gitaly/automatic_failover_and_recovery_spec.rb": 124.591377774,
"qa/specs/features/api/12_systems/gitaly/backend_node_recovery_spec.rb": 133.341350153,
"qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb": 135.629715876,
"qa/specs/features/api/12_systems/gitaly/gitaly_mtls_spec.rb": 8.710134297,
"qa/specs/features/api/1_manage/import/import_github_repo_spec.rb": 94.836860872,
"qa/specs/features/api/1_manage/integrations/webhook_events_spec.rb": 55.202470212,
"qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb": 71.068568299,
"qa/specs/features/api/1_manage/migration/gitlab_migration_issue_spec.rb": 191.40671086700002,
"qa/specs/features/api/1_manage/migration/gitlab_migration_pipeline_spec.rb": 101.418912323,
"qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 86.199285414,
"qa/specs/features/api/1_manage/rate_limits_spec.rb": 13.681232201,
"qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 16.628659891,
"qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 18.632759778,
"qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 40.50874593,
"qa/specs/features/api/3_create/merge_request/push_options_spec.rb": 40.941381925,
"qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 1.984831665,
"qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 19.166037203,
"qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 10.386859875,
"qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 14.950358283,
"qa/specs/features/api/3_create/repository/files_spec.rb": 5.399797597,
"qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 13.926035241,
"qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 20.095130139,
"qa/specs/features/api/3_create/repository/storage_size_spec.rb": 17.40260055,
"qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 4.557645028,
"qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 102.59533486,
"qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 17.921089144,
"qa/specs/features/api/4_verify/file_variable_spec.rb": 108.77503098,
"qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 60.320143223,
"qa/specs/features/api/8_monitor/metrics_spec.rb": 4.849474337,
"qa/specs/features/api/9_data_stores/users_spec.rb": 0.892267347,
"qa/specs/features/api/9_tenant_scale/user_inherited_access_spec.rb": 131.43909578199998,
"qa/specs/features/api/9_tenant_scale/users_spec.rb": 5.447678849,
"qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 21.318932887,
"qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 47.737133234,
"qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 48.143979841,
"qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 14.860763952,
"qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 103.087909674,
"qa/specs/features/browser_ui/10_govern/login/log_into_gitlab_via_ldap_spec.rb": 3.496607815,
"qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb": 29.053194068,
"qa/specs/features/browser_ui/10_govern/login/login_via_instance_wide_saml_sso_spec.rb": 15.806100889,
"qa/specs/features/browser_ui/10_govern/login/oauth_login_with_github_spec.rb": 40.665469502,
"qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 96.334226178,
"qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 24.673522444,
"qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 32.700644966,
"qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 34.641708789,
"qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 34.703431313,
"qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 11.914625589,
"qa/specs/features/browser_ui/14_analytics/service_ping_disabled_spec.rb": 12.014757008,
"qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 72.586710287,
"qa/specs/features/browser_ui/1_manage/integrations/jira/jira_basic_integration_spec.rb": 63.084658112,
"qa/specs/features/browser_ui/1_manage/integrations/pipeline_status_emails_spec.rb": 73.852005486,
"qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb": 53.950460633,
"qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 21.000879009,
"qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 28.953349875,
"qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 26.645472257,
"qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb": 13.756195215,
"qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 29.575440439,
"qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 24.926239844,
"qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 23.023338104,
"qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 151.174229924,
"qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 30.057413526,
"qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 38.970955318,
"qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 29.299008757,
"qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 23.720133674,
"qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 36.179742962,
"qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 26.324370987,
"qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 18.079963204,
"qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 105.340186238,
"qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 21.671163394,
"qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 25.749516379,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 71.814413183,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 52.613164620999996,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 20.068066802,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 34.591485112,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 58.819139284,
"qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 47.277272998,
"qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 23.255745385,
"qa/specs/features/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 127.168158595,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 67.252757943,
"qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 32.223963051,
"qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 87.827782154,
"qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 53.056355204,
"qa/specs/features/browser_ui/3_create/merge_request/merge_request_set_to_auto_merge_spec.rb": 87.360235814,
"qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 31.587529235,
"qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 52.606350146,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 63.193462575,
"qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 66.920456466,
"qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 29.99328968,
"qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 18.334742894,
"qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 20.731932415,
"qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 21.381189561,
"qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 29.900971524,
"qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 39.629157223,
"qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 93.708429757,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 18.17010505,
"qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 21.111061798,
"qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 22.452186483,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 63.882396272,
"qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 62.198666296,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 47.464493569,
"qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 33.633618531,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_file_size_spec.rb": 61.309425759999996,
"qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 45.961936588,
"qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 10.71170848,
"qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 23.203424045,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 25.000182089,
"qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 27.827771667,
"qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 44.768375755,
"qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 40.496648039,
"qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 25.161237139,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 45.836259861,
"qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 41.939930087,
"qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 25.903048665,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 13.900035191,
"qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 11.621989822,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 27.377261185,
"qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 17.18307556,
"qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 34.146785136,
"qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 23.542113286000003,
"qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 65.335500732,
"qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 20.716304489,
"qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 45.115673919,
"qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 65.61314646,
"qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 18.933166402,
"qa/specs/features/browser_ui/3_create/web_ide/settings_sync_web_ide_spec.rb": 170.151622798,
"qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 131.685566209,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 85.14900900399999,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_glab_spec.rb": 165.436427342,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/release_with_release_cli_spec.rb": 113.980604624,
"qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 46.65707505,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 43.341571268,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 250.111015097,
"qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 289.840771931,
"qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 71.028508365,
"qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 144.433797235,
"qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 54.491041314,
"qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 94.980152438,
"qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 29.989966111,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 97.780808817,
"qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 59.438863121,
"qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 99.436951084,
"qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 83.332001529,
"qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 99.977753257,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 84.607064656,
"qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 76.262548031,
"qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 24.490188353,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_registration_token_spec.rb": 17.938743554,
"qa/specs/features/browser_ui/4_verify/runner/deprecated_unregister_runner_spec.rb": 29.797505381,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_counts_spec.rb": 26.805126272,
"qa/specs/features/browser_ui/4_verify/runner/fleet_visibility/group_runner_status_counts_spec.rb": 17.624983578,
"qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 20.85656348,
"qa/specs/features/browser_ui/4_verify/runner/register_project_runner_spec.rb": 68.961966963,
"qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 58.42357976,
"qa/specs/features/browser_ui/5_package/container_registry/self_managed/container_registry_spec.rb": 326.001061823,
"qa/specs/features/browser_ui/5_package/dependency_proxy/dependency_proxy_spec.rb": 166.521857226,
"qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 53.397001906,
"qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 71.812714409,
"qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 49.3065106,
"qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 275.762812457,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 563.7325411840001,
"qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 338.291903648,
"qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 308.187804595,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 270.596665232,
"qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 259.061583186,
"qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 78.865083326,
"qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 33.046605622,
"qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 165.311982478,
"qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 10.313825005,
"qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 55.240105387,
"qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 63.256907842000004,
"qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 50.802184255,
"qa/specs/features/browser_ui/8_monitor/alert_management/email_notification_for_alert_spec.rb": 62.301000474,
"qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 26.296569993,
"qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 22.594858998,
"qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 24.599204923000002,
"qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 23.850562164,
"qa/specs/features/browser_ui/9_tenant_scale/group/create_group_with_mattermost_team_spec.rb": 6.240378808,
"qa/specs/features/browser_ui/9_tenant_scale/group/group_member_access_request_spec.rb": 60.852950331,
"qa/specs/features/browser_ui/9_tenant_scale/group/transfer_project_spec.rb": 34.070024556,
"qa/specs/features/browser_ui/9_tenant_scale/project/add_project_member_spec.rb": 32.422878768,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_badge_spec.rb": 18.46602993,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_spec.rb": 65.529161432,
"qa/specs/features/browser_ui/9_tenant_scale/project/dashboard_images_spec.rb": 14.00007171,
"qa/specs/features/browser_ui/9_tenant_scale/project/invite_group_to_project_spec.rb": 54.089279590000004,
"qa/specs/features/browser_ui/9_tenant_scale/project/project_owner_permissions_spec.rb": 88.32154976999999,
"qa/specs/features/browser_ui/9_tenant_scale/project/view_project_activity_spec.rb": 31.978578499,
"qa/specs/features/browser_ui/9_tenant_scale/user/follow_user_activity_spec.rb": 18.7066108,
"qa/specs/features/browser_ui/9_tenant_scale/user/parent_group_access_termination_spec.rb": 23.543098987,
"qa/specs/features/browser_ui/9_tenant_scale/user/user_inherited_access_spec.rb": 30.211737995,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 60.564013815,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 31.675662473,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 60.127438927,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 155.987421953,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 14.192521699,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 69.474014124,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 63.176276094,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 43.533472471,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 112.02314393200001,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/elasticsearch_api_spec.rb": 35.638105102,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 22.440969574,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 150.585063896,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/main_index/blob_index_spec.rb": 19.133683791,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 49.420091828,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/notes_index/note_index_spec.rb": 51.884753885,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/user_index/user_index_spec.rb": 53.562935587,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 108.20761873200001,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 68.107695752,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 89.465203516,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 31.659377304,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 156.79193972,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 66.973654801,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 136.07338086,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 113.822288455,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 110.51489972799999,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 281.034084289,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 99.384373985,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 146.648856414,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 63.87743802999999,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 157.077619561,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 116.88328403300001,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 85.77617316300001,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 344.98750820000004,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 19.211998915,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 31.769016921,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 259.54058256400003,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 10.309449618,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 4.82132239,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 14.228443253,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 41.162109412,
"qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 125.18084846,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 102.143966936,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 120.70963605899999,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 12.988818241,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 58.641500210000004,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 38.7953483,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 58.647171594,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 38.314743697,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 15.599344136,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 15.578684487,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 208.45408638,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 41.716152389,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 7.349980713,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 29.777405177,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 15.870823101,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 42.317240466,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 17.819557861,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 31.566727651,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 19.621939433,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 27.649151253,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 20.289821851,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 22.098904729,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 45.135074855999996,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 26.535937236,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 12.082668441,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 23.925967119,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 44.864304038,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 19.451816733,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 40.885643196,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 43.112278353,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 61.966444696,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 22.993249051,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 107.20613004,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 42.0947788,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 79.86792701,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 28.892405643,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 152.138745253,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 23.68209186,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 169.404830288,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 133.96777923,
"qa/specs/features/ee/browser_ui/3_create/repository/prevent_forking_outside_group_spec.rb": 41.2943997,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 85.309391562,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 44.83058516,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 63.87730038,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 321.3376084590001,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 174.94060700300003,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 157.489280599,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 126.869427415,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 126.336214883,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 45.157701197,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 65.373292235,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 17.547394519,
"qa/specs/features/ee/browser_ui/9_tenant_scale/elasticsearch/elasticsearch_reindexing_spec.rb": 110.16069590800001,
"qa/specs/features/ee/browser_ui/9_tenant_scale/group/share_group_with_group_spec.rb": 21.356710449
"qa/specs/features/browser_ui/9_tenant_scale/group/create_group_with_mattermost_team_spec.rb": 5.987978583,
"qa/specs/features/browser_ui/9_tenant_scale/group/group_member_access_request_spec.rb": 57.246668183000004,
"qa/specs/features/browser_ui/9_tenant_scale/group/transfer_project_spec.rb": 28.280268834,
"qa/specs/features/browser_ui/9_tenant_scale/project/add_project_member_spec.rb": 31.780745051,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_badge_spec.rb": 26.777403345,
"qa/specs/features/browser_ui/9_tenant_scale/project/create_project_spec.rb": 56.256326088,
"qa/specs/features/browser_ui/9_tenant_scale/project/dashboard_images_spec.rb": 11.742786971000001,
"qa/specs/features/browser_ui/9_tenant_scale/project/invite_group_to_project_spec.rb": 48.748845588,
"qa/specs/features/browser_ui/9_tenant_scale/project/project_owner_permissions_spec.rb": 149.051296687,
"qa/specs/features/browser_ui/9_tenant_scale/project/view_project_activity_spec.rb": 28.05712424,
"qa/specs/features/browser_ui/9_tenant_scale/user/follow_user_activity_spec.rb": 31.198744317,
"qa/specs/features/browser_ui/9_tenant_scale/user/parent_group_access_termination_spec.rb": 22.908094528,
"qa/specs/features/browser_ui/9_tenant_scale/user/user_inherited_access_spec.rb": 23.664832740999998,
"qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 45.694278522,
"qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 25.447950898000002,
"qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 73.251858542,
"qa/specs/features/ee/api/1_manage/import/import_github_repo_spec.rb": 57.19293936,
"qa/specs/features/ee/api/1_manage/integrations/group_webhook_events_spec.rb": 6.68710722,
"qa/specs/features/ee/api/1_manage/migration/gitlab_migration_group_spec.rb": 72.219462862,
"qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 59.702647653999996,
"qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 42.72711256800001,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/advanced_global_advanced_syntax_search_spec.rb": 87.817043927,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/elasticsearch_api_spec.rb": 36.310745342000004,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/commit_index/commit_index_spec.rb": 100.588870488,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/issues_index/issue_index_spec.rb": 19.536915797,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/main_index/blob_index_spec.rb": 20.439565261,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/merge_request_index/merge_request_index_spec.rb": 59.197920659,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/notes_index/note_index_spec.rb": 73.771512509,
"qa/specs/features/ee/api/9_tenant_scale/elasticsearch/index_tests/user_index/user_index_spec.rb": 40.680925353,
"qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 101.22409271500001,
"qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 79.662903667,
"qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 113.813310238,
"qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 18.775476413,
"qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 174.872336133,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 45.216024108,
"qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 108.507464134,
"qa/specs/features/ee/browser_ui/10_govern/group/group_ldap_sync_spec.rb": 112.39822228399998,
"qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 109.94306708199998,
"qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb": 234.487165319,
"qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 130.770201629,
"qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 157.561304826,
"qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 59.975743636999994,
"qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 202.804135304,
"qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 141.810384486,
"qa/specs/features/ee/browser_ui/10_govern/security_policies_spec.rb": 89.99680464100001,
"qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 385.28500405799997,
"qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 20.459961271,
"qa/specs/features/ee/browser_ui/10_govern/vulnerabilities_jira_integration_spec.rb": 28.492389793,
"qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 402.2089694900001,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/cloud_activation_spec.rb": 20.540360151,
"qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 6.081126495,
"qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 17.358051514,
"qa/specs/features/ee/browser_ui/13_secure/cvs_dependency_scanning_spec.rb": 72.98500408,
"qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 143.345512721,
"qa/specs/features/ee/browser_ui/13_secure/secret_push_protection_spec.rb": 91.26585501,
"qa/specs/features/ee/browser_ui/16_ai_powered/duo_chat/duo_chat_spec.rb": 10.269040891,
"qa/specs/features/ee/browser_ui/1_manage/integrations/jira_issues_list_spec.rb": 61.9165773,
"qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 80.390964641,
"qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 65.521365178,
"qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 42.192420010999996,
"qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 22.278444452,
"qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 17.130871428,
"qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 206.29039720699998,
"qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 44.791174013,
"qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 10.236663233,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 34.580014542,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 9.37836252,
"qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 26.474749558,
"qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 27.173981141,
"qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 33.944733478,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 15.361843155,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 25.387587103,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 17.323462934,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 27.294679076,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 41.109234448,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 26.964102879,
"qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 19.494462026,
"qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 13.801373899,
"qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 25.73753042,
"qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 13.644869101,
"qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 44.496911121000004,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 39.641629496,
"qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 44.079304436,
"qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 25.876180949,
"qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 94.024174037,
"qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 31.8476772,
"qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 47.59685267,
"qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 27.825299577,
"qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 137.34111622799998,
"qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 29.259726312,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 94.80818762300001,
"qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 180.399954169,
"qa/specs/features/ee/browser_ui/3_create/repository/prevent_forking_outside_group_spec.rb": 42.672418057,
"qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 80.48694758100001,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 37.078803079,
"qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 57.964661088,
"qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 303.83190287,
"qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 253.695409001,
"qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 170.830466145,
"qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 112.459076308,
"qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 180.83007212,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_for_merged_result_spec.rb": 29.78252656,
"qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 56.542371793,
"qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 21.442962804,
"qa/specs/features/ee/browser_ui/9_tenant_scale/elasticsearch/elasticsearch_reindexing_spec.rb": 151.28373901700002,
"qa/specs/features/ee/browser_ui/9_tenant_scale/group/share_group_with_group_spec.rb": 36.903485776
}

View File

@ -3,29 +3,29 @@
module RuboCop
module Cop
module API
# This cop checks that APIs subclass API::Base.
#
# @example
#
# # bad
# module API
# class Projects < Grape::API
# end
# end
#
# module API
# class Projects < Grape::API::Instance
# end
# end
#
# # good
# module API
# class Projects < ::API::Base
# end
# end
class Base < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector
# This cop checks that APIs subclass API::Base.
#
# @example
#
# # bad
# module API
# class Projects < Grape::API
# end
# end
#
# module API
# class Projects < Grape::API::Instance
# end
# end
#
# # good
# module API
# class Projects < ::API::Base
# end
# end
MSG = 'Inherit from ::API::Base instead of Grape::API::Instance or Grape::API. ' \
'For more details check https://gitlab.com/gitlab-org/gitlab/-/issues/215230.'

View File

@ -5,29 +5,28 @@ require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module API
# This cop checks that `allow_access_with_scope` is called only at the class level.
# This is because `allow_access_with_scope` aggregates scopes for each call in a class.
# Calling `allow_access_with_scope` within a `namespace` or an alias method such as
# `resource`, `resources`, `segment` or `group` may mislead developers to think the scope
# would be only allowed within given namespace which is not the case.
#
# @example
#
# # bad
# class MyClass < ::API::Base
# include APIGuard
# namespace 'my_namespace' do
# resource :my_resource do
# allow_access_with_scope :ai_workflows
#
# # good
# class MyClass < ::API::Base
# include APIGuard
# allow_access_with_scope :ai_workflows
class ClassLevelAllowAccessWithScope < RuboCop::Cop::Base
include CodeReuseHelpers
# This cop checks that `allow_access_with_scope` is called only at the class level.
# This is because `allow_access_with_scope` aggregates scopes for each call in a class.
# Calling `allow_access_with_scope` within a `namespace` or an alias method such as
# `resource`, `resources`, `segment` or `group` may mislead developers to think the scope
# would be only allowed within given namespace which is not the case.
#
# @example
#
# # bad
# class MyClass < ::API::Base
# include APIGuard
# namespace 'my_namespace' do
# resource :my_resource do
# allow_access_with_scope :ai_workflows
#
# # good
# class MyClass < ::API::Base
# include APIGuard
# allow_access_with_scope :ai_workflows
#
MSG = '`allow_access_with_scope` should only be called on class-level and not within a namespace.'
# In Grape::DSL::Routing::ClassMethods

View File

@ -5,23 +5,22 @@ require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module API
# This cop checks that API detail entries use Strings
#
# https://gitlab.com/gitlab-org/gitlab/-/issues/379037
#
# @example
#
# # bad
# detail ['Foo bar baz bat', 'http://example.com']
#
# # good
# detail 'Foo bar baz bat. http://example.com'
#
# end
class EnsureStringDetail < RuboCop::Cop::Base
include CodeReuseHelpers
# This cop checks that API detail entries use Strings
#
# https://gitlab.com/gitlab-org/gitlab/-/issues/379037
#
# @example
#
# # bad
# detail ['Foo bar baz bat', 'http://example.com']
#
# # good
# detail 'Foo bar baz bat. http://example.com'
#
# end
#
MSG = 'Only String objects are permitted in API detail field.'
def_node_matcher :detail_in_desc, <<~PATTERN

View File

@ -3,21 +3,21 @@
module RuboCop
module Cop
module API
# This cop checks that Grape API parameters using an Array type
# implement a coerce_with method:
#
# https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions
#
# @example
#
# # bad
# requires :values, type: Array[String]
#
# # good
# requires :values, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce
#
# end
class GrapeArrayMissingCoerce < RuboCop::Cop::Base
# This cop checks that Grape API parameters using an Array type
# implement a coerce_with method:
#
# https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions
#
# @example
#
# # bad
# requires :values, type: Array[String]
#
# # good
# requires :values, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce
#
# end
MSG = 'This Grape parameter defines an Array but is missing a coerce_with definition. ' \
'For more details, see https://github.com/ruby-grape/grape/blob/master/UPGRADING.md#ensure-that-array-types-have-explicit-coercions'

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'rubocop-rspec'
module RuboCop
module Cop
module Gitlab
module Ai
# This cop enforces the order of the ConfigFiles::Constants.
#
# @example
#
# # bad
# [ConfigFiles::PythonPoetry, ConfigFiles::CConanPy, ConfigFiles::CConanTxt]
# [ConfigFiles::PythonPoetry, ConfigFiles::PythonPoetryLock, ConfigFiles::RubyGemsLock]
#
# # good
# [ConfigFiles::CConanPy, ConfigFiles::CConanTxt, ConfigFiles::PythonPoetry]
# [ConfigFiles::PythonPoetryLock, ConfigFiles::PythonPoetry, ConfigFiles::RubyGemsLock]
#
class OrderConstants < RuboCop::Cop::Base
MSG = 'Order lock files by language (alphabetically), then by precedence. ' \
'Lock files should appear first before their non-lock file counterparts.'
# @!method config_file_classes(node)
def_node_matcher :config_file_classes, <<~PATTERN
$(
casgn nil? :CONFIG_FILE_CLASSES (send $array ...)
)
PATTERN
# @!method config_files_constants?(node)
def_node_matcher :config_files_constants?, <<~PATTERN
$(module
(const nil? :ConfigFiles)
(module
(const nil? :Constants)
...
)
)
PATTERN
def on_casgn(node)
# we want to make sure that we are running the cop only on
# ConfigFiles::Constants::CONFIG_FILES_CONSTANTS
return unless config_files_constants?(node.parent.parent)
_matcher, constants_array = config_file_classes(node)
constants_names = constants_array.child_nodes.map(&:source)
return if constants_names == sort_with_lock_priority(constants_names)
add_offense(node)
end
private
def sort_with_lock_priority(config_file_classes)
base_classes = config_file_classes.group_by { |class_name| class_name.gsub("Lock", "") }
base_classes.each_value do |classes|
classes.sort! do |a, b|
if b.include?("Lock")
1
else
(a.include?("Lock") ? -1 : (a <=> b))
end
end
end
# Sort the base names alphabetically and flatten the result
base_classes.keys.sort.flat_map { |base_name| base_classes[base_name] }
end
end
end
end
end
end

View File

@ -7,18 +7,18 @@ module RuboCop
#
# @example
#
# # bad
# # bad
#
# Feature.get(:x).enable
# Feature.get(:x).enable_percentage_of_time(100)
# Feature.get(:x).remove
# Feature.get(:x).enable
# Feature.get(:x).enable_percentage_of_time(100)
# Feature.get(:x).remove
#
# # good
# # good
#
# stub_feature_flags(x: true)
# Feature.enable(:x)
# Feature.enable_percentage_of_time(:x, 100)
# Feature.remove(:x)
# stub_feature_flags(x: true)
# Feature.enable(:x)
# Feature.enable_percentage_of_time(:x, 100)
# Feature.remove(:x)
#
class AvoidFeatureGet < RuboCop::Cop::Base
MSG = 'Use `stub_feature_flags` method instead of `Feature.get`. ' \

View File

@ -9,15 +9,15 @@ module RuboCop
#
# @example
#
# # bad
# # bad
#
# hash.keys.first
# hash.values.first
# hash.keys.first
# hash.values.first
#
# # good
# # good
#
# hash.each_key.first
# hash.each_value.first
# hash.each_key.first
# hash.each_value.first
#
class KeysFirstAndValuesFirst < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector

View File

@ -8,20 +8,20 @@ module RuboCop
#
# @example
#
# # bad, `conducts_electricity` returns a Rule object, not a boolean!
# rule { conducts_electricity && batteries }.enable :light_bulb
# # bad, `conducts_electricity` returns a Rule object, not a boolean!
# rule { conducts_electricity && batteries }.enable :light_bulb
#
# # good
# rule { conducts_electricity & batteries }.enable :light_bulb
# # good
# rule { conducts_electricity & batteries }.enable :light_bulb
#
# @example
#
# # bad, `conducts_electricity` returns a Rule object, so the ternary is always going to be true
# rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
# # bad, `conducts_electricity` returns a Rule object, so the ternary is always going to be true
# rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
#
# # good
# rule { conducts_electricity & can?(:magnetize) }.enable :motor
# rule { ~conducts_electricity & batteries }.enable :motor
# # good
# rule { conducts_electricity & can?(:magnetize) }.enable :motor
# rule { ~conducts_electricity & batteries }.enable :motor
class PolicyRuleBoolean < RuboCop::Cop::Base
def_node_search :has_and_operator?, <<~PATTERN
(and ...)

View File

@ -1,52 +1,51 @@
# frozen_string_literal: true
# This cop checks for missing GraphQL descriptions and enforces the description style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#description-style-guide
#
# @safety
# This cop is unsafe because not all cases of "this" can be substituted with
# "the". This will require a technical writer to assist with the alternative,
# proper grammar that can be used for that particular GraphQL descriptions.
#
# @examples
#
# # bad
# class AwfulType
# field :some_field, GraphQL::Types::String
# end
#
# class TerribleType
# argument :some_argument, GraphQL::Types::String
# end
#
# class UngoodType
# field :some_argument,
# GraphQL::Types::String,
# description: "A description that does not end in a period"
# end
#
# class BadEnum
# value "some_value"
# end
#
# # good
# class GreatType
# argument :some_field,
# GraphQL::Types::String,
# description: "Well described - a superb description."
#
# field :some_field,
# GraphQL::Types::String,
# description: "Thorough and compelling description."
# end
#
# class GoodEnum
# value "some_value", "Good description."
# end
module RuboCop
module Cop
module Graphql
# This cop checks for missing GraphQL descriptions and enforces the description style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#description-style-guide
#
# @note
# This cop is unsafe because not all cases of "this" can be substituted with
# "the". This will require a technical writer to assist with the alternative,
# proper grammar that can be used for that particular GraphQL descriptions.
#
# @example
#
# # bad
# class AwfulType
# field :some_field, GraphQL::Types::String
# end
#
# class TerribleType
# argument :some_argument, GraphQL::Types::String
# end
#
# class UngoodType
# field :some_argument,
# GraphQL::Types::String,
# description: "A description that does not end in a period"
# end
#
# class BadEnum
# value "some_value"
# end
#
# # good
# class GreatType
# argument :some_field,
# GraphQL::Types::String,
# description: "Well described - a superb description."
#
# field :some_field,
# GraphQL::Types::String,
# description: "Thorough and compelling description."
# end
#
# class GoodEnum
# value "some_value", "Good description."
# end
class Descriptions < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector

View File

@ -1,37 +1,36 @@
# frozen_string_literal: true
# This cop enforces the enum naming conventions from the enum style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums
#
# @example
#
# # bad
# class FooBar < BaseEnum
# value 'FOO'
# end
#
# class SubparEnum < BaseEnum
# end
#
# class UngoodEnum < BaseEnum
# graphql_name 'UngoodEnum'
# end
#
# # good
#
# class GreatEnum < BaseEnum
# graphql_name 'Great'
#
# value 'BAR'
# end
#
# class NiceEnum < BaseEnum
# declarative_enum NiceDeclarativeEnum
# end
module RuboCop
module Cop
module Graphql
# This cop enforces the enum naming conventions from the enum style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums
#
# @example
#
# # bad
# class FooBar < BaseEnum
# value 'FOO'
# end
#
# class SubparEnum < BaseEnum
# end
#
# class UngoodEnum < BaseEnum
# graphql_name 'UngoodEnum'
# end
#
# # good
#
# class GreatEnum < BaseEnum
# graphql_name 'Great'
#
# value 'BAR'
# end
#
# class NiceEnum < BaseEnum
# declarative_enum NiceDeclarativeEnum
# end
class EnumNames < RuboCop::Cop::Base
SEE_SG_MSG = "See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums"
CLASS_NAME_SUFFIX_MSG = "Enum class names must end with `Enum`. #{SEE_SG_MSG}".freeze

View File

@ -1,43 +1,42 @@
# frozen_string_literal: true
# This cop enforces the enum value conventions from the enum style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums
#
# @example
#
# # bad
# class BadEnum < BaseEnum
# graphql_name 'Bad'
#
# value 'foo'
# end
#
# class UngoodEnum < BaseEnum
# graphql_name 'Ungood'
#
# ['bar'].each do |val|
# value val
# end
# end
#
# # good
# class GoodEnum < BaseEnum
# graphql_name 'Good'
#
# value 'FOO'
# end
#
# class GreatEnum < BaseEnum
# graphql_name 'Great'
#
# ['bar'].each do |val|
# value val.upcase
# end
# end
module RuboCop
module Cop
module Graphql
# This cop enforces the enum value conventions from the enum style guide:
# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums
#
# @example
#
# # bad
# class BadEnum < BaseEnum
# graphql_name 'Bad'
#
# value 'foo'
# end
#
# class UngoodEnum < BaseEnum
# graphql_name 'Ungood'
#
# ['bar'].each do |val|
# value val
# end
# end
#
# # good
# class GoodEnum < BaseEnum
# graphql_name 'Good'
#
# value 'FOO'
# end
#
# class GreatEnum < BaseEnum
# graphql_name 'Great'
#
# ['bar'].each do |val|
# value val.upcase
# end
# end
class EnumValues < RuboCop::Cop::Base
MSG = "Enum values must either be an uppercase string literal or uppercased with the `upcase` method. " \
"See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums"

View File

@ -1,25 +1,24 @@
# frozen_string_literal: true
# This cop ensures that if a class uses `graphql_name`, then
# it's the first line of the class
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::Types::JSON
# graphql_name 'AwfulClass'
# end
#
# # good
# class GreatClass
# graphql_name 'AwfulClass'
# field :some_field, GraphQL::Types::String
# end
module RuboCop
module Cop
module Graphql
# This cop ensures that if a class uses `graphql_name`, then
# it's the first line of the class
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::Types::JSON
# graphql_name 'AwfulClass'
# end
#
# # good
# class GreatClass
# graphql_name 'AwfulClass'
# field :some_field, GraphQL::Types::String
# end
class GraphqlNamePosition < RuboCop::Cop::Base
MSG = '`graphql_name` should be the first line of the class: '\
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#naming-conventions'

View File

@ -1,23 +1,22 @@
# frozen_string_literal: true
# This cop checks for use of GraphQL::Types::JSON types in GraphQL fields
# and arguments.
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::Types::JSON
# end
#
# # good
# class GreatClass
# field :some_field, GraphQL::Types::String
# end
module RuboCop
module Cop
module Graphql
# This cop checks for use of GraphQL::Types::JSON types in GraphQL fields
# and arguments.
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::Types::JSON
# end
#
# # good
# class GreatClass
# field :some_field, GraphQL::Types::String
# end
class JSONType < RuboCop::Cop::Base
MSG = 'Avoid using GraphQL::Types::JSON. See: ' \
'https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#json'

View File

@ -1,24 +1,23 @@
# frozen_string_literal: true
# This cop checks for use of older GraphQL types in GraphQL fields
# and arguments.
# GraphQL::ID_TYPE, GraphQL::INT_TYPE, GraphQL::STRING_TYPE, GraphQL::BOOLEAN_TYPE
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::STRING_TYPE
# end
#
# # good
# class GreatClass
# field :some_field, GraphQL::Types::String
# end
module RuboCop
module Cop
module Graphql
# This cop checks for use of older GraphQL types in GraphQL fields
# and arguments.
# GraphQL::ID_TYPE, GraphQL::INT_TYPE, GraphQL::STRING_TYPE, GraphQL::BOOLEAN_TYPE
#
# @example
#
# # bad
# class AwfulClass
# field :some_field, GraphQL::STRING_TYPE
# end
#
# # good
# class GreatClass
# field :some_field, GraphQL::Types::String
# end
class OldTypes < RuboCop::Cop::Base
MSG_ID = 'Avoid using GraphQL::ID_TYPE. Use GraphQL::Types::ID instead'
MSG_INT = 'Avoid using GraphQL::INT_TYPE. Use GraphQL::Types::Int instead'

View File

@ -1,28 +1,27 @@
# frozen_string_literal: true
# This cop checks for missing GraphQL type annotations on resolvers
#
# @example
#
# # bad
# module Resolvers
# class NoTypeResolver < BaseResolver
# field :some_field, GraphQL::Types::String
# end
# end
#
# # good
# module Resolvers
# class WithTypeResolver < BaseResolver
# type MyType, null: true
#
# field :some_field, GraphQL::Types::String
# end
# end
module RuboCop
module Cop
module Graphql
# This cop checks for missing GraphQL type annotations on resolvers
#
# @example
#
# # bad
# module Resolvers
# class NoTypeResolver < BaseResolver
# field :some_field, GraphQL::Types::String
# end
# end
#
# # good
# module Resolvers
# class WithTypeResolver < BaseResolver
# type MyType, null: true
#
# field :some_field, GraphQL::Types::String
# end
# end
class ResolverType < RuboCop::Cop::Base
MSG = 'Missing type annotation: Please add `type` DSL method call. ' \
'e.g: type UserType.connection_type, null: true'

View File

@ -7,7 +7,7 @@ module RuboCop
module Migration
# Cop that prevents introducing `encrypted_*` columns (used by the `attr_encrypted` gem).
#
# @examples
# @example
#
# # bad
# class CreateAuditEventsInstanceAmazonS3Configurations < ActiveRecord::Migration[6.0]

View File

@ -14,7 +14,7 @@ module RuboCop
# This cop was introduced to clarify the need for disable_ddl_transaction!
# and to avoid bike-shedding and review back-and-forth.
#
# @examples
# @example
#
# # bad
# class SomeMigration < Gitlab::Database::Migration[2.1]

View File

@ -11,7 +11,7 @@ module RuboCop
#
# @example
#
# # bad
# # bad
#
# page.has_css?('[data-testid="begin-commit-button"]') ? find('[data-testid="begin-commit-button"]').click : nil
#

View File

@ -10,25 +10,25 @@ module RuboCop
#
# @example
#
# # bad
# it 'expects a snowplow event' do
# expect(Gitlab::Tracking).to receive(:event).with("Category", "action", ...)
# end
# # bad
# it 'expects a snowplow event' do
# expect(Gitlab::Tracking).to receive(:event).with("Category", "action", ...)
# end
#
# # good
# it 'expects a snowplow event', :snowplow do
# expect_snowplow_event(category: "Category", action: "action", ...)
# end
# # good
# it 'expects a snowplow event', :snowplow do
# expect_snowplow_event(category: "Category", action: "action", ...)
# end
#
# # bad
# it 'does not expect a snowplow event' do
# expect(Gitlab::Tracking).not_to receive(:event)
# end
# # bad
# it 'does not expect a snowplow event' do
# expect(Gitlab::Tracking).not_to receive(:event)
# end
#
# # good
# it 'does not expect a snowplow event', :snowplow do
# expect_no_snowplow_event
# end
# # good
# it 'does not expect a snowplow event', :snowplow do
# expect_no_snowplow_event
# end
class ExpectGitlabTracking < RuboCop::Cop::Base
MSG = 'Do not expect directly on `Gitlab::Tracking#event`, add the `snowplow` annotation and use ' \
'`expect_snowplow_event` instead. ' \

View File

@ -13,37 +13,37 @@ module RuboCop
#
# @example
#
# Context:
# Context:
#
# Factory.define do
# factory :project, class: 'Project'
# # EXAMPLE below
# Factory.define do
# factory :project, class: 'Project'
# # EXAMPLE below
# end
# end
#
# # bad
# creator { create(:user) }
# creator { create(:user, :admin) }
# creator { build(:user) }
# creator { FactoryBot.build(:user) }
# creator { ::FactoryBot.build(:user) }
# add_attribute(:creator) { build(:user) }
#
# # good
# creator { association(:user) }
# creator { association(:user, :admin) }
# add_attribute(:creator) { association(:user) }
#
# # Accepted
# after(:build) do |instance|
# instance.creator = create(:user)
# end
#
# # bad
# creator { create(:user) }
# creator { create(:user, :admin) }
# creator { build(:user) }
# creator { FactoryBot.build(:user) }
# creator { ::FactoryBot.build(:user) }
# add_attribute(:creator) { build(:user) }
# initialize_with do
# create(:project)
# end
#
# # good
# creator { association(:user) }
# creator { association(:user, :admin) }
# add_attribute(:creator) { association(:user) }
#
# # Accepted
# after(:build) do |instance|
# instance.creator = create(:user)
# end
#
# initialize_with do
# create(:project)
# end
#
# creator_id { create(:user).id }
# creator_id { create(:user).id }
#
class InlineAssociation < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector

View File

@ -12,16 +12,16 @@ module RuboCop
#
# @example
#
# # bad
# expect(response).to have_http_status(200)
# expect(response).to have_http_status(:ok)
# expect(response).to have_gitlab_http_status(200)
# expect(response.status).to eq(200)
# expect(response.status).not_to eq(200)
# # bad
# expect(response).to have_http_status(200)
# expect(response).to have_http_status(:ok)
# expect(response).to have_gitlab_http_status(200)
# expect(response.status).to eq(200)
# expect(response.status).not_to eq(200)
#
# # good
# expect(response).to have_gitlab_http_status(:ok)
# expect(response).not_to have_gitlab_http_status(:ok)
# # good
# expect(response).to have_gitlab_http_status(:ok)
# expect(response).not_to have_gitlab_http_status(:ok)
#
class HaveGitlabHttpStatus < RuboCop::Cop::Base
extend RuboCop::Cop::AutoCorrector

View File

@ -10,9 +10,9 @@ module RuboCop
#
# @example
#
# # bad
# histogram(Issue, buckets: 1..100)
# histogram(User.active, buckets: 1..100)
# # bad
# histogram(Issue, buckets: 1..100)
# histogram(User.active, buckets: 1..100)
class HistogramWithLargeTable < RuboCop::Cop::Base
include UsageDataHelpers

View File

@ -5,24 +5,24 @@ require_relative '../../usage_data_helpers'
module RuboCop
module Cop
module UsageData
# This cop checks that batch count and distinct_count are used in usage_data.rb files in metrics based on ActiveRecord models.
#
# @example
#
# # bad
# Issue.count
# List.assignee.count
# ::Ci::Pipeline.auto_devops_source.count
# ZoomMeeting.distinct.count(:issue_id)
#
# # Good
# count(Issue)
# count(List.assignee)
# count(::Ci::Pipeline.auto_devops_source)
# distinct_count(ZoomMeeting, :issue_id)
class LargeTable < RuboCop::Cop::Base
include UsageDataHelpers
# This cop checks that batch count and distinct_count are used in usage_data.rb files in metrics based on ActiveRecord models.
#
# @example
#
# # bad
# Issue.count
# List.assignee.count
# ::Ci::Pipeline.auto_devops_source.count
# ZoomMeeting.distinct.count(:issue_id)
#
# # Good
# count(Issue)
# count(List.assignee)
# count(::Ci::Pipeline.auto_devops_source)
# distinct_count(ZoomMeeting, :issue_id)
MSG = 'Use one of the %{count_methods} methods for counting on %{class_name}'
# Match one level const as Issue, Gitlab

View File

@ -25,7 +25,6 @@ describe('Packages Protection Rule Form', () => {
projectPath: 'path',
glFeatures: {
packagesProtectedPackagesConan: true,
packagesProtectedPackagesMaven: true,
packagesProtectedPackagesDelete: true,
},
};
@ -114,23 +113,6 @@ describe('Packages Protection Rule Form', () => {
expect(packageTypeSelectOptions()).toEqual(['MAVEN', 'NPM', 'PYPI']);
});
});
describe('when feature flag packagesProtectedPackagesMaven is disabled', () => {
it('contains available options without option "MAVEN"', () => {
mountComponent({
provide: {
...defaultProvidedValues,
glFeatures: {
...defaultProvidedValues.glFeatures,
packagesProtectedPackagesMaven: false,
},
},
});
expect(findPackageTypeSelect().exists()).toBe(true);
expect(packageTypeSelectOptions()).toEqual(['CONAN', 'NPM', 'PYPI']);
});
});
});
describe('form field "minimumAccessLevelForPushSelect"', () => {

View File

@ -1,5 +1,5 @@
import { cloneDeep } from 'lodash';
import { WIDGET_TYPE_HIERARCHY } from '~/work_items/constants';
import { WIDGET_TYPE_HIERARCHY, WIDGET_TYPE_CUSTOM_FIELDS } from '~/work_items/constants';
import {
addHierarchyChild,
removeHierarchyChild,
@ -359,6 +359,11 @@ describe('work items graphql cache utils', () => {
timelogs: { __typename: 'WorkItemTimelogConnection', nodes: [] },
totalTimeSpent: 0,
},
{
__typename: 'WorkItemWidgetCustomFields',
type: WIDGET_TYPE_CUSTOM_FIELDS,
customFieldValues: null,
},
],
},
},
@ -447,6 +452,10 @@ describe('work items graphql cache utils', () => {
editable: false,
rollUp: true,
},
{
__typename: 'WorkItemWidgetDefinitionCustomFields',
type: WIDGET_TYPE_CUSTOM_FIELDS,
},
],
'EPIC',
'gid://gitlab/WorkItems::Type/8 ',

View File

@ -5885,6 +5885,7 @@ export const createWorkItemQueryResponse = {
},
__typename: 'WorkItemWidgetWeight',
},
customFieldsWidgetResponseFactory(),
],
__typename: 'WorkItem',
},

View File

@ -1329,6 +1329,28 @@ RSpec.describe MergeRequest, factory_default: :keep, feature_category: :code_rev
create(:note, :internal, noteable: merge_request, note: issue_referenced_in_internal_mr_note.to_reference)
end
context 'feature flag: more_commits_from_gitaly' do
let_it_be(:user) { create(:user, guest_of: project) }
it 'loads commits from Gitaly' do
expect(merge_request).to receive(:commits).with(load_from_gitaly: true).and_call_original
related_issues
end
context 'when "more_commits_from_gitaly" is disabled' do
before do
stub_feature_flags(more_commits_from_gitaly: false)
end
it 'loads commits from DB' do
expect(merge_request).to receive(:commits).with(load_from_gitaly: false).and_call_original
related_issues
end
end
end
context 'for guest' do
let_it_be(:user) { create(:user, guest_of: project) }

View File

@ -1026,14 +1026,6 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq '403 Forbidden - Package protected.'
end
context 'when feature flag :packages_protected_packages_maven is disabled' do
before do
stub_feature_flags(packages_protected_packages_maven: false)
end
it_behaves_like 'authorized package'
end
end
context 'for personal access token' do
@ -1415,14 +1407,6 @@ RSpec.describe API::MavenPackages, feature_category: :package_registry do
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq '403 Forbidden - Package protected.'
end
context 'when feature flag :packages_protected_packages_maven is disabled' do
before do
stub_feature_flags(packages_protected_packages_maven: false)
end
it_behaves_like 'package workhorse uploads'
end
end
context 'for personal access token' do

View File

@ -28,7 +28,6 @@ RSpec.describe Projects::Settings::PackagesAndRegistriesController, feature_cate
end
it_behaves_like 'pushed feature flag', :packages_protected_packages_conan
it_behaves_like 'pushed feature flag', :packages_protected_packages_maven
it_behaves_like 'pushed feature flag', :packages_protected_packages_delete
it_behaves_like 'pushed feature flag', :container_registry_protected_tags
end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require_relative '../../../../../rubocop/cop/gitlab/ai/order_constants'
RSpec.describe RuboCop::Cop::Gitlab::Ai::OrderConstants, feature_category: :dependency_management do
context 'when not in the expected order' do
it 'registers an offense when not in alphabetical order' do
expect_offense(<<~RUBY)
module Ai
module Context
module Dependencies
module ConfigFiles
module Constants
CONFIG_FILE_CLASSES = [
^^^^^^^^^^^^^^^^^^^^^^^ Order lock files by language (alphabetically), then by precedence. Lock files should appear first before their non-lock file counterparts.
ConfigFiles::PythonPoetry,
ConfigFiles::CConanPy,
ConfigFiles::CConanTxt
].freeze
end
end
end
end
end
RUBY
end
it 'registers an offense when the lock file is before the non-lock file' do
expect_offense(<<~RUBY)
module Ai
module Context
module Dependencies
module ConfigFiles
module Constants
CONFIG_FILE_CLASSES = [
^^^^^^^^^^^^^^^^^^^^^^^ Order lock files by language (alphabetically), then by precedence. Lock files should appear first before their non-lock file counterparts.
ConfigFiles::JavaMaven,
ConfigFiles::JavascriptNpm,
ConfigFiles::JavascriptNpmLock,
].freeze
end
end
end
end
end
RUBY
end
it 'registers an offense when the lock file is after the non-lock file and not in alphabetical order' do
expect_offense(<<~RUBY)
module Ai
module Context
module Dependencies
module ConfigFiles
module Constants
CONFIG_FILE_CLASSES = [
^^^^^^^^^^^^^^^^^^^^^^^ Order lock files by language (alphabetically), then by precedence. Lock files should appear first before their non-lock file counterparts.
ConfigFiles::JavaMaven,
ConfigFiles::KotlinGradle,
ConfigFiles::JavascriptNpm,
ConfigFiles::JavascriptNpmLock,
ConfigFiles::PhpComposerLock,
ConfigFiles::PhpComposer,
].freeze
end
end
end
end
end
RUBY
end
end
end

View File

@ -131,14 +131,6 @@ RSpec.describe Packages::Maven::CreatePackageService, feature_category: :package
.and not_change { Packages::Package.maven.count }
.and not_change { Packages::PackageFile.count }
end
context 'when feature flag :packages_protected_packages_maven is disabled' do
before do
stub_feature_flags(packages_protected_packages_maven: false)
end
it_behaves_like 'valid package'
end
end
shared_examples 'an error service response for unauthorized' do