Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/test-on-gdk/main.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/graphql_shared/queries/project_autocomplete_users.query.graphql12
-rw-r--r--app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql19
-rw-r--r--app/assets/javascripts/sidebar/constants.js8
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue10
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/users/destroy_service.rb3
-rw-r--r--config/feature_flags/development/limited_access_modal.yml8
-rw-r--r--config/feature_flags/development/scan_execution_bot_users.yml8
-rw-r--r--db/post_migrate/20230825085648_ensure_backfill_for_ci_stages_pipeline_id_is_finished.rb23
-rw-r--r--db/post_migrate/20230825085719_create_async_index_for_ci_stages_pipeline_id.rb31
-rw-r--r--db/schema_migrations/202308250856481
-rw-r--r--db/schema_migrations/202308250857191
-rw-r--r--doc/.vale/gitlab/BadgeCapitalization.yml4
-rw-r--r--doc/.vale/gitlab/Uppercase.yml2
-rw-r--r--doc/api/graphql/reference/index.md5
-rw-r--r--doc/ci/runners/saas/macos_saas_runner.md2
-rw-r--r--doc/security/two_factor_authentication.md11
-rw-r--r--doc/user/application_security/policies/scan-execution-policies.md4
-rw-r--r--doc/user/product_analytics/index.md1
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md2
-rw-r--r--doc/user/project/pages/getting_started_part_one.md3
-rw-r--r--gems/gitlab-schema-validation/lib/gitlab/schema/validation/inconsistency.rb11
-rw-r--r--gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/inconsistency_spec.rb19
-rw-r--r--lib/api/discussions.rb2
-rw-r--r--lib/gitlab/ci/components/instance_path.rb45
-rw-r--r--lib/gitlab/utils/markdown.rb2
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json2
-rw-r--r--spec/factories/issues.rb6
-rw-r--r--spec/frontend/sidebar/mock_data.js80
-rw-r--r--spec/frontend/vue_shared/components/user_select_spec.js28
-rw-r--r--spec/lib/gitlab/ci/components/instance_path_spec.rb174
-rw-r--r--spec/lib/gitlab/utils/markdown_spec.rb35
-rw-r--r--spec/requests/api/discussions_spec.rb11
-rw-r--r--spec/services/ci/components/fetch_service_spec.rb37
-rw-r--r--spec/services/groups/destroy_service_spec.rb8
-rw-r--r--yarn.lock18
38 files changed, 427 insertions, 225 deletions
diff --git a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
index 41f85c492d9..482736ad1ac 100644
--- a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
+++ b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
@@ -125,6 +125,7 @@ download-fast-quarantine-report:
gdk-qa-smoke:
extends:
- .gdk-qa-base
+ - .gitlab-qa-report
variables:
QA_SCENARIO: Test::Instance::Smoke
QA_RUN_TYPE: gdk-qa-smoke
@@ -152,6 +153,7 @@ gdk-qa-smoke-with-load-balancer:
gdk-qa-reliable:
extends:
- .gdk-qa-base
+ - .gitlab-qa-report
- .parallel
variables:
QA_SCENARIO: Test::Instance::Blocking
diff --git a/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users.query.graphql b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users.query.graphql
new file mode 100644
index 00000000000..ed318ef1b8d
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users.query.graphql
@@ -0,0 +1,12 @@
+#import "../fragments/user.fragment.graphql"
+#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
+
+query projectAutocompleteUsersSearch($search: String!, $fullPath: ID!) {
+ workspace: project(fullPath: $fullPath) {
+ id
+ users: autocompleteUsers(search: $search) {
+ ...User
+ ...UserAvailability
+ }
+ }
+}
diff --git a/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql
new file mode 100644
index 00000000000..8155451fb7c
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql
@@ -0,0 +1,19 @@
+#import "../fragments/user.fragment.graphql"
+#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
+
+query projectAutocompleteUsersSearchWithMRPermissions(
+ $search: String!
+ $fullPath: ID!
+ $mergeRequestId: MergeRequestID!
+) {
+ workspace: project(fullPath: $fullPath) {
+ id
+ users: autocompleteUsers(search: $search) {
+ ...User
+ ...UserAvailability
+ mergeRequestInteraction(id: $mergeRequestId) {
+ canMerge
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 0f82182c6e2..752d0315227 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -1,8 +1,8 @@
import { invert } from 'lodash';
import { s__, __, sprintf } from '~/locale';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
-import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
-import userSearchWithMRPermissionsQuery from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
+import userAutocompleteQuery from '~/graphql_shared/queries/project_autocomplete_users.query.graphql';
+import userAutocompleteWithMRPermissionsQuery from '~/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql';
import {
TYPE_ALERT,
TYPE_EPIC,
@@ -106,10 +106,10 @@ export const participantsQueries = {
export const userSearchQueries = {
[TYPE_ISSUE]: {
- query: userSearchQuery,
+ query: userAutocompleteQuery,
},
[TYPE_MERGE_REQUEST]: {
- query: userSearchWithMRPermissionsQuery,
+ query: userAutocompleteWithMRPermissionsQuery,
},
};
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index 4879baced0d..1e79d2cdcd7 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -130,11 +130,11 @@ export default {
},
update(data) {
return (
- data.workspace?.users?.nodes
- .filter((x) => x?.user)
- .map((node) => ({
- ...node.user,
- canMerge: node.mergeRequestInteraction?.canMerge || false,
+ data.workspace?.users
+ .filter((user) => user)
+ .map((user) => ({
+ ...user,
+ canMerge: user.mergeRequestInteraction?.canMerge || false,
})) || []
);
},
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 0b54de469f8..a896ca5cabc 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -14,8 +14,6 @@ module Groups
# TODO - add a policy check here https://gitlab.com/gitlab-org/gitlab/-/issues/353082
raise DestroyError, "You can't delete this group because you're blocked." if current_user.blocked?
- group.prepare_for_destroy
-
group.projects.includes(:project_feature).each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
success = ::Projects::DestroyService.new(project, current_user).execute
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 3c0ba1514e5..a0e1167836b 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -59,9 +59,6 @@ module Users
Groups::DestroyService.new(group, current_user).execute
end
- namespace = user.namespace
- namespace.prepare_for_destroy
-
user.personal_projects.each do |project|
success = ::Projects::DestroyService.new(project, current_user).execute
raise DestroyError, "Project #{project.id} can't be deleted" unless success
diff --git a/config/feature_flags/development/limited_access_modal.yml b/config/feature_flags/development/limited_access_modal.yml
new file mode 100644
index 00000000000..b567b9ce0d4
--- /dev/null
+++ b/config/feature_flags/development/limited_access_modal.yml
@@ -0,0 +1,8 @@
+---
+name: limited_access_modal
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129790
+rollout_issue_url:
+milestone: '16.4'
+type: development
+group: group::billing and subscription management
+default_enabled: false
diff --git a/config/feature_flags/development/scan_execution_bot_users.yml b/config/feature_flags/development/scan_execution_bot_users.yml
deleted file mode 100644
index ca06e666e67..00000000000
--- a/config/feature_flags/development/scan_execution_bot_users.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: scan_execution_bot_users
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118089
-rollout_issue_url:
-milestone: '16.0'
-type: development
-group: group::security policies
-default_enabled: true
diff --git a/db/post_migrate/20230825085648_ensure_backfill_for_ci_stages_pipeline_id_is_finished.rb b/db/post_migrate/20230825085648_ensure_backfill_for_ci_stages_pipeline_id_is_finished.rb
new file mode 100644
index 00000000000..3dabd352a1b
--- /dev/null
+++ b/db/post_migrate/20230825085648_ensure_backfill_for_ci_stages_pipeline_id_is_finished.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class EnsureBackfillForCiStagesPipelineIdIsFinished < Gitlab::Database::Migration[2.1]
+ include Gitlab::Database::MigrationHelpers::ConvertToBigint
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+ disable_ddl_transaction!
+
+ TABLE_NAME = :ci_stages
+
+ def up
+ ensure_batched_background_migration_is_finished(
+ job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
+ table_name: TABLE_NAME,
+ column_name: 'id',
+ job_arguments: [['pipeline_id'], ['pipeline_id_convert_to_bigint']]
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20230825085719_create_async_index_for_ci_stages_pipeline_id.rb b/db/post_migrate/20230825085719_create_async_index_for_ci_stages_pipeline_id.rb
new file mode 100644
index 00000000000..a517d96815a
--- /dev/null
+++ b/db/post_migrate/20230825085719_create_async_index_for_ci_stages_pipeline_id.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class CreateAsyncIndexForCiStagesPipelineId < Gitlab::Database::Migration[2.1]
+ TABLE_NAME = :ci_stages
+ INDEXES = {
+ 'index_ci_stages_on_pipeline_id_convert_to_bigint_and_name' => [
+ [:pipeline_id_convert_to_bigint, :name], { unique: true }
+ ],
+ 'index_ci_stages_on_pipeline_id_convert_to_bigint' => [
+ [:pipeline_id_convert_to_bigint], {}
+ ],
+ 'index_ci_stages_on_pipeline_id_convert_to_bigint_and_id' => [
+ [:pipeline_id_convert_to_bigint, :id], { where: 'status = ANY (ARRAY[0, 1, 2, 8, 9, 10])' }
+ ],
+ 'index_ci_stages_on_pipeline_id_convert_to_bigint_and_position' => [
+ [:pipeline_id_convert_to_bigint, :position], {}
+ ]
+ }
+
+ def up
+ INDEXES.each do |index_name, (columns, options)|
+ prepare_async_index TABLE_NAME, columns, name: index_name, **options
+ end
+ end
+
+ def down
+ INDEXES.each do |index_name, (columns, options)|
+ unprepare_async_index TABLE_NAME, columns, name: index_name, **options
+ end
+ end
+end
diff --git a/db/schema_migrations/20230825085648 b/db/schema_migrations/20230825085648
new file mode 100644
index 00000000000..a6b1d8e1be1
--- /dev/null
+++ b/db/schema_migrations/20230825085648
@@ -0,0 +1 @@
+5e003d34a36320c53852ece7d0373ce99e7fc21b08f8edc5f5320256d4b3b3a2 \ No newline at end of file
diff --git a/db/schema_migrations/20230825085719 b/db/schema_migrations/20230825085719
new file mode 100644
index 00000000000..cf785c0a170
--- /dev/null
+++ b/db/schema_migrations/20230825085719
@@ -0,0 +1 @@
+4b7b8711a29a8a26ff9af42d73b95eb52b1791569771b1c34f6f51000059b10d \ No newline at end of file
diff --git a/doc/.vale/gitlab/BadgeCapitalization.yml b/doc/.vale/gitlab/BadgeCapitalization.yml
index 6e77c3fe822..a44bcbc0a7d 100644
--- a/doc/.vale/gitlab/BadgeCapitalization.yml
+++ b/doc/.vale/gitlab/BadgeCapitalization.yml
@@ -10,5 +10,5 @@ link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html
level: error
scope: raw
raw:
- - '(?!\*\*\((FREE|PREMIUM|ULTIMATE)( (SELF|SAAS))?\)\*\*)'
- - '(?i)\*\*\((free|premium|ultimate)( (self|saas))?\)\*\*'
+ - '(?!\*\*\((FREE|PREMIUM|ULTIMATE)( (SELF|SAAS|ALL) (BETA|EXPERIMENT))?\)\*\*)'
+ - '(?i)\*\*\((free|premium|ultimate)( (self|saas|all) (beta|experiment))?\)\*\*'
diff --git a/doc/.vale/gitlab/Uppercase.yml b/doc/.vale/gitlab/Uppercase.yml
index 4730184b950..b13ebe2c0a8 100644
--- a/doc/.vale/gitlab/Uppercase.yml
+++ b/doc/.vale/gitlab/Uppercase.yml
@@ -16,6 +16,7 @@ second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
exceptions:
- ACL
- AJAX
+ - ALL
- AMI
- ANSI
- APAC
@@ -27,6 +28,7 @@ exceptions:
- ASG
- AST
- AWS
+ - BETA
- BMP
- BSD
- CAS
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 90d27749b0c..246610ecd4c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1152,7 +1152,7 @@ Input type: `AiActionInput`
| <a id="mutationaiactiongeneratecommitmessage"></a>`generateCommitMessage` | [`AiGenerateCommitMessageInput`](#aigeneratecommitmessageinput) | Input for generate_commit_message AI action. |
| <a id="mutationaiactiongeneratedescription"></a>`generateDescription` | [`AiGenerateDescriptionInput`](#aigeneratedescriptioninput) | Input for generate_description AI action. |
| <a id="mutationaiactiongeneratetestfile"></a>`generateTestFile` | [`GenerateTestFileInput`](#generatetestfileinput) | Input for generate_test_file AI action. |
-| <a id="mutationaiactionmarkupformat"></a>`markupFormat` | [`MarkupFormat`](#markupformat) | Indicates the response format. |
+| <a id="mutationaiactionmarkupformat"></a>`markupFormat` **{warning-solid}** | [`MarkupFormat`](#markupformat) | **Deprecated:** Moved to contentHtml attribute. Deprecated in 16.4. |
| <a id="mutationaiactionsummarizecomments"></a>`summarizeComments` | [`AiSummarizeCommentsInput`](#aisummarizecommentsinput) | Input for summarize_comments AI action. |
| <a id="mutationaiactionsummarizereview"></a>`summarizeReview` | [`AiSummarizeReviewInput`](#aisummarizereviewinput) | Input for summarize_review AI action. |
| <a id="mutationaiactiontanukibot"></a>`tanukiBot` | [`AiTanukiBotInput`](#aitanukibotinput) | Input for tanuki_bot AI action. |
@@ -12539,6 +12539,7 @@ Information about a connected Agent.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="aicachedmessagetypecontent"></a>`content` | [`String`](#string) | Content of the message. Can be null for failed responses. |
+| <a id="aicachedmessagetypecontenthtml"></a>`contentHtml` | [`String`](#string) | HTML content of the message. Can be null for failed responses. |
| <a id="aicachedmessagetypeerrors"></a>`errors` | [`[String!]!`](#string) | Errors that occurred while asynchronously fetching an AI (assistant) response. |
| <a id="aicachedmessagetypeid"></a>`id` | [`ID`](#id) | UUID of the message. |
| <a id="aicachedmessagetyperequestid"></a>`requestId` | [`ID`](#id) | UUID of the original request message. |
@@ -12566,6 +12567,7 @@ Information about a connected Agent.
| <a id="airesponseerrors"></a>`errors` | [`[String!]`](#string) | Errors return by AI API as response. |
| <a id="airesponserequestid"></a>`requestId` | [`String`](#string) | ID of the original request. |
| <a id="airesponseresponsebody"></a>`responseBody` | [`String`](#string) | Response body from AI API. |
+| <a id="airesponseresponsebodyhtml"></a>`responseBodyHtml` | [`String`](#string) | Response body HTML. |
| <a id="airesponserole"></a>`role` | [`AiCachedMessageRole!`](#aicachedmessagerole) | Message role. |
| <a id="airesponsetimestamp"></a>`timestamp` | [`Time!`](#time) | Message timestamp. |
| <a id="airesponsetype"></a>`type` | [`String`](#string) | Message type. |
@@ -27160,7 +27162,6 @@ List markup formats.
| Value | Description |
| ----- | ----------- |
| <a id="markupformathtml"></a>`HTML` | HTML format. |
-| <a id="markupformatmarkdown"></a>`MARKDOWN` | Markdown format. |
| <a id="markupformatraw"></a>`RAW` | Raw format. |
### `MeasurementIdentifier`
diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md
index 036bf187bb9..982db707525 100644
--- a/doc/ci/runners/saas/macos_saas_runner.md
+++ b/doc/ci/runners/saas/macos_saas_runner.md
@@ -56,7 +56,7 @@ GitLab provides `stable` and `latest` macOS images that follow different update
By definition, the `latest` images are always Beta.
A `latest` image is not available.
-### Image release process**
+### Image release process
When Apple releases a new macOS version, GitLab releases both `stable` and `latest` images based on the OS in the next release. Both images are Beta.
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 9d53ddae60d..e0042277b99 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -8,9 +8,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Enforce two-factor authentication **(FREE ALL)**
[Two-factor authentication (2FA)](../user/profile/account/two_factor_authentication.md)
-provides an additional level of security to your users' GitLab account. When enabled,
-users are prompted for a code generated by an application in addition to supplying
-their username and password to sign in.
+is an authentication method that requires the user to provide two different factors
+to prove their identity:
+
+- Username and password.
+- A second authentication method, such as a code generated by an application.
+
+2FA makes it harder for an unauthorized person to access an account because
+they would need both factors.
NOTE:
If you are [using and enforcing SSO](../user/group/saml_sso/index.md#sso-enforcement), you might already be enforcing 2FA on the identity provider (IDP) side. Enforcing 2FA on GitLab as well might be unnecessary.
diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md
index dc59d615b65..59aa880ff9f 100644
--- a/doc/user/application_security/policies/scan-execution-policies.md
+++ b/doc/user/application_security/policies/scan-execution-policies.md
@@ -113,10 +113,10 @@ This rule enforces the defined actions whenever the pipeline runs for a selected
> - The `branch_type` field was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/404774) in GitLab 16.1 [with a flag](../../../administration/feature_flags.md) named `security_policies_branch_type`. Disabled by default.
> - The `branch_type` field was [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/413062) in GitLab 16.2.
-> - The security policy bot users were [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/394958) in GitLab 16.3 [with flags](../../../administration/feature_flags.md) named `scan_execution_group_bot_users` and `scan_execution_bot_users`. Enabled by default.
+> - The security policy bot users were [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/394958) in GitLab 16.3 [with flag](../../../administration/feature_flags.md) named `scan_execution_group_bot_users`. Enabled by default.
FLAG:
-On self-managed GitLab, security policy bot users are available. To hide the feature, an administrator can [disable the feature flags](../../../administration/feature_flags.md) named `scan_execution_group_bot_users` and `scan_execution_bot_users`.
+On self-managed GitLab, security policy bot users are available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_execution_group_bot_users`.
On GitLab.com, this feature is available.
This rule schedules a scan pipeline, enforcing the defined actions on the schedule defined in the `cadence` field. A scheduled pipeline does not run other jobs defined in the project's `.gitlab-ci.yml` file. When a project is linked to a security policy project, a security policy bot is created in the project and will become the author of any scheduled pipelines.
diff --git a/doc/user/product_analytics/index.md b/doc/user/product_analytics/index.md
index 7c8b21ab3d6..e1d4fc48f2f 100644
--- a/doc/user/product_analytics/index.md
+++ b/doc/user/product_analytics/index.md
@@ -24,6 +24,7 @@ This feature is not ready for production use.
This page is a work in progress, and we're updating the information as we add more features.
For more information, see the [group direction page](https://about.gitlab.com/direction/analytics/product-analytics/).
+To leave feedback about Product Analytics bugs or functionality, please comment in [issue 391970](https://gitlab.com/gitlab-org/gitlab/-/issues/391970) or open a new issue with the label `group::product analytics`.
## How product analytics works
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index fb8022a1048..d8e4fce41ef 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -113,7 +113,7 @@ Subdomains (`subdomain.example.com`) require:
Whether it's a user or a project website, the DNS record
should point to your Pages domain (`namespace.gitlab.io`),
-without any path (for example, `/project-slug`).
+without any path.
![DNS `CNAME` record pointing to GitLab.com project](img/dns_cname_record_example.png)
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index e3750152a29..ec511ee0a5f 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -25,14 +25,13 @@ For GitLab self-managed instances, replace `example.io`
with your instance's Pages domain. For GitLab.com,
Pages domains are `*.gitlab.io`.
-| Type of GitLab Pages | The path of the project created in GitLab | Website URL |
+| Type of GitLab Pages | Example path of a project in GitLab | Website URL |
| -------------------- | ------------ | ----------- |
| User pages | `username/username.example.io` | `http(s)://username.example.io` |
| Group pages | `acmecorp/acmecorp.example.io` | `http(s)://acmecorp.example.io` |
| Project pages owned by a user | `username/my-website` | `http(s)://username.example.io/my-website` |
| Project pages owned by a group | `acmecorp/webshop` | `http(s)://acmecorp.example.io/webshop`|
| Project pages owned by a subgroup | `acmecorp/documentation/product-manual` | `http(s)://acmecorp.example.io/documentation/product-manual`|
-| Project pages owned by a subgroup | `group-path/subgroup-slug/project-slug` | `http(s)://group-path.example.io/subgroup-slug/project-slug`|
WARNING:
There are some known [limitations](introduction.md#subdomains-of-subdomains)
diff --git a/gems/gitlab-schema-validation/lib/gitlab/schema/validation/inconsistency.rb b/gems/gitlab-schema-validation/lib/gitlab/schema/validation/inconsistency.rb
index 13799b8b9ff..503e05f12e9 100644
--- a/gems/gitlab-schema-validation/lib/gitlab/schema/validation/inconsistency.rb
+++ b/gems/gitlab-schema-validation/lib/gitlab/schema/validation/inconsistency.rb
@@ -36,6 +36,17 @@ module Gitlab
Diffy::Diff.new(structure_sql_statement, database_statement)
end
+ def to_h
+ {
+ type: type,
+ object_type: object_type,
+ table_name: table_name,
+ object_name: object_name,
+ structure_sql_statement: structure_sql_statement,
+ database_statement: database_statement
+ }
+ end
+
def display
<<~MSG
#{'-' * 54}
diff --git a/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/inconsistency_spec.rb b/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/inconsistency_spec.rb
index 268bb4556e3..300383d5909 100644
--- a/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/inconsistency_spec.rb
+++ b/gems/gitlab-schema-validation/spec/lib/gitlab/schema/validation/inconsistency_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Schema::Validation::Inconsistency do
+RSpec.describe Gitlab::Schema::Validation::Inconsistency, feature_category: :database do
let(:validator) { Gitlab::Schema::Validation::Validators::DifferentDefinitionIndexes }
let(:database_statement) { 'CREATE INDEX index_name ON public.achievements USING btree (namespace_id)' }
@@ -44,6 +44,23 @@ RSpec.describe Gitlab::Schema::Validation::Inconsistency do
end
end
+ describe '#to_h' do
+ let(:result) do
+ {
+ database_statement: inconsistency.database_statement,
+ object_name: inconsistency.object_name,
+ object_type: inconsistency.object_type,
+ structure_sql_statement: inconsistency.structure_sql_statement,
+ table_name: inconsistency.table_name,
+ type: inconsistency.type
+ }
+ end
+
+ it 'returns the to_h of the validator' do
+ expect(inconsistency.to_h).to eq(result)
+ end
+ end
+
describe '#table_name' do
it 'returns the table name' do
expect(inconsistency.table_name).to eq('achievements')
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 45466a1894c..5c2337cdde4 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -76,7 +76,7 @@ module API
requires :base_sha, type: String, desc: 'Base commit SHA in the source branch'
requires :start_sha, type: String, desc: 'SHA referencing commit in target branch'
requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request'
- requires :position_type, type: String, desc: 'Type of the position reference', values: %w(text image)
+ requires :position_type, type: String, desc: 'Type of the position reference', values: %w(text image file)
optional :new_path, type: String, desc: 'File path after change'
optional :new_line, type: Integer, desc: 'Line number after change'
optional :old_path, type: String, desc: 'File path before change'
diff --git a/lib/gitlab/ci/components/instance_path.rb b/lib/gitlab/ci/components/instance_path.rb
index 7ccfd195108..17c784c4d54 100644
--- a/lib/gitlab/ci/components/instance_path.rb
+++ b/lib/gitlab/ci/components/instance_path.rb
@@ -13,12 +13,13 @@ module Gitlab
address.include?('@') && address.start_with?(Settings.gitlab_ci['component_fqdn'])
end
- attr_reader :host
+ attr_reader :host, :project_file_path
def initialize(address:, content_filename:)
@full_path, @version = address.to_s.split('@', 2)
@content_filename = content_filename
@host = Settings.gitlab_ci['component_fqdn']
+ @project_file_path = nil
end
def fetch_content!(current_user:)
@@ -27,7 +28,7 @@ module Gitlab
raise Gitlab::Access::AccessDeniedError unless Ability.allowed?(current_user, :download_code, project)
- templates_dir_path_content || content(sha, custom_dir_template_file_path)
+ content(simple_template_path) || content(complex_template_path) || content(legacy_template_path)
end
def project
@@ -43,12 +44,6 @@ module Gitlab
end
strong_memoize_attr :sha
- def project_file_path
- return unless project
-
- custom_dir_template_file_path
- end
-
private
attr_reader :version, :path
@@ -58,7 +53,7 @@ module Gitlab
end
def component_path
- instance_path.delete_prefix(project.full_path)
+ instance_path.delete_prefix(project.full_path).delete_prefix('/')
end
strong_memoize_attr :component_path
@@ -81,31 +76,33 @@ module Gitlab
project.releases.latest&.sha
end
- def custom_dir_template_file_path
- File.join(component_path, @content_filename).delete_prefix('/')
- end
+ # A simple template consists of a single file
+ def simple_template_path
+ # Extract this line and move to fetch_content once we remove legacy fetching
+ return unless templates_dir_exists? && component_path.index('/').nil?
- def templates_dir_file_path
- File.join(TEMPLATES_DIR, "#{component_path}.yml")
+ @project_file_path = File.join(TEMPLATES_DIR, "#{component_path}.yml")
end
+ # A complex template is directory-based and may consist of multiple files.
# Given a path like "my-org/sub-group/the-project/templates/component"
- # returns "templates/component/template.yml"
- def templates_dir_template_file_path
- File.join(TEMPLATES_DIR, component_path, @content_filename)
- end
+ # returns the entry point path: "templates/component/template.yml".
+ def complex_template_path
+ # Extract this line and move to fetch_content once we remove legacy fetching
+ return unless templates_dir_exists? && component_path.index('/').nil?
- def templates_dir_exists?
- project.repository.tree.trees.map(&:name).include?(TEMPLATES_DIR)
+ @project_file_path = File.join(TEMPLATES_DIR, component_path, @content_filename)
end
- def templates_dir_path_content
- return unless templates_dir_exists?
+ def legacy_template_path
+ @project_file_path = File.join(component_path, @content_filename).delete_prefix('/')
+ end
- content(sha, templates_dir_file_path) || content(sha, templates_dir_template_file_path)
+ def templates_dir_exists?
+ project.repository.tree.trees.map(&:name).include?(TEMPLATES_DIR)
end
- def content(sha, path)
+ def content(path)
project.repository.blob_data_at(sha, path)
end
end
diff --git a/lib/gitlab/utils/markdown.rb b/lib/gitlab/utils/markdown.rb
index c041953e7c8..68e9206b4d5 100644
--- a/lib/gitlab/utils/markdown.rb
+++ b/lib/gitlab/utils/markdown.rb
@@ -4,7 +4,7 @@ module Gitlab
module Utils
module Markdown
PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
- PRODUCT_SUFFIX = /\s*\**\((core|starter|premium|ultimate|free|bronze|silver|gold)(\s+(all|only|self|saas))?\)\**/.freeze
+ PRODUCT_SUFFIX = /\s*\**\((premium|ultimate|free|beta|experiment)(\s+(all|self|saas))?(\s+(beta|experiment))?\)\**/.freeze
def string_to_anchor(string)
string
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6303647a4d2..7ca4705cf81 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -45791,6 +45791,18 @@ msgstr ""
msgid "SubscriptionBanner|Upload new license"
msgstr ""
+msgid "SubscriptionMangement|If you'd like to add more seats, upgrade your plan, or purchase additional products, contact your GitLab sales representative."
+msgstr ""
+
+msgid "SubscriptionMangement|This is a custom subscription managed by the GitLab Sales team"
+msgstr ""
+
+msgid "SubscriptionMangement|To make changes to a read-only subscription or purchase additional products, contact your GitLab Partner."
+msgstr ""
+
+msgid "SubscriptionMangement|Your subscription is in read-only mode"
+msgstr ""
+
msgid "SubscriptionTable|Add seats"
msgstr ""
diff --git a/package.json b/package.json
index 006be9fabed..637bb99f021 100644
--- a/package.json
+++ b/package.json
@@ -251,7 +251,7 @@
"cheerio": "^1.0.0-rc.9",
"commander": "^2.20.3",
"custom-jquery-matchers": "^2.1.0",
- "eslint": "8.47.0",
+ "eslint": "8.48.0",
"eslint-import-resolver-jest": "3.0.2",
"eslint-import-resolver-webpack": "0.13.7",
"eslint-plugin-import": "^2.28.1",
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index b0db17875c5..7d044c4aa92 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -71,6 +71,12 @@ FactoryBot.define do
association :author, factory: :user
end
+ trait :user_namespace_level do
+ project { nil }
+ association :namespace, factory: :user_namespace
+ association :author, factory: :user
+ end
+
trait :issue do
association :work_item_type, :default, :issue
end
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 05a7f504fd4..9d8392ad5f0 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -414,6 +414,33 @@ export const searchQueryResponse = {
},
};
+export const searchAutocompleteQueryResponse = {
+ data: {
+ workspace: {
+ __typename: 'Project',
+ id: '',
+ users: [
+ {
+ id: '1',
+ avatarUrl: '/avatar',
+ name: 'root',
+ username: 'root',
+ webUrl: 'root',
+ status: null,
+ },
+ {
+ id: '2',
+ avatarUrl: '/avatar2',
+ name: 'rookie',
+ username: 'rookie',
+ webUrl: 'rookie',
+ status: null,
+ },
+ ],
+ },
+ },
+};
+
export const updateIssueAssigneesMutationResponse = {
data: {
issuableSetAssignees: {
@@ -545,6 +572,29 @@ export const searchResponseOnMR = {
},
};
+export const searchAutocompleteResponseOnMR = {
+ data: {
+ workspace: {
+ __typename: 'Project',
+ id: '1',
+ users: [
+ {
+ ...mockUser1,
+ mergeRequestInteraction: {
+ canMerge: true,
+ },
+ },
+ {
+ ...mockUser2,
+ mergeRequestInteraction: {
+ canMerge: false,
+ },
+ },
+ ],
+ },
+ },
+};
+
export const projectMembersResponse = {
data: {
workspace: {
@@ -585,6 +635,36 @@ export const projectMembersResponse = {
},
};
+export const projectAutocompleteMembersResponse = {
+ data: {
+ workspace: {
+ id: '1',
+ __typename: 'Project',
+ users: [
+ // Remove nulls https://gitlab.com/gitlab-org/gitlab/-/issues/329750
+ null,
+ null,
+ // Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822
+ mockUser1,
+ mockUser1,
+ mockUser2,
+ {
+ __typename: 'UserCore',
+ id: 'gid://gitlab/User/2',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
+ name: 'Jacki Kub',
+ username: 'francina.skiles',
+ webUrl: '/franc',
+ status: {
+ availability: 'BUSY',
+ },
+ },
+ ],
+ },
+ },
+};
+
export const groupMembersResponse = {
data: {
workspace: {
diff --git a/spec/frontend/vue_shared/components/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js
index 8c7657da8bc..119b892392f 100644
--- a/spec/frontend/vue_shared/components/user_select_spec.js
+++ b/spec/frontend/vue_shared/components/user_select_spec.js
@@ -5,17 +5,17 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import searchUsersQuery from '~/graphql_shared/queries/users_search.query.graphql';
-import searchUsersQueryOnMR from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
+import searchUsersQuery from '~/graphql_shared/queries/project_autocomplete_users.query.graphql';
+import searchUsersQueryOnMR from '~/graphql_shared/queries/project_autocomplete_users_with_mr_permissions.query.graphql';
import { TYPE_MERGE_REQUEST } from '~/issues/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
import getIssueParticipantsQuery from '~/sidebar/queries/get_issue_participants.query.graphql';
import UserSelect from '~/vue_shared/components/user_select/user_select.vue';
import {
- searchResponse,
- searchResponseOnMR,
- projectMembersResponse,
+ projectAutocompleteMembersResponse,
+ searchAutocompleteQueryResponse,
+ searchAutocompleteResponseOnMR,
participantsQueryResponse,
mockUser1,
mockUser2,
@@ -59,7 +59,7 @@ describe('User select dropdown', () => {
const findUnassignLink = () => wrapper.findByTestId('unassign');
const findEmptySearchResults = () => wrapper.findAllByTestId('empty-results');
- const searchQueryHandlerSuccess = jest.fn().mockResolvedValue(projectMembersResponse);
+ const searchQueryHandlerSuccess = jest.fn().mockResolvedValue(projectAutocompleteMembersResponse);
const participantsQueryHandlerSuccess = jest.fn().mockResolvedValue(participantsQueryResponse);
const createComponent = ({
@@ -69,7 +69,7 @@ describe('User select dropdown', () => {
} = {}) => {
fakeApollo = createMockApollo([
[searchUsersQuery, searchQueryHandler],
- [searchUsersQueryOnMR, jest.fn().mockResolvedValue(searchResponseOnMR)],
+ [searchUsersQueryOnMR, jest.fn().mockResolvedValue(searchAutocompleteResponseOnMR)],
[getIssueParticipantsQuery, participantsQueryHandler],
]);
wrapper = shallowMountExtended(UserSelect, {
@@ -200,7 +200,7 @@ describe('User select dropdown', () => {
});
await waitForPromises();
- expect(findUnselectedParticipantByIndex(0).props('user')).toEqual(mockUser2);
+ expect(findUnselectedParticipantByIndex(0).props('user')).toMatchObject(mockUser2);
});
it('moves issuable author on top of unassigned list after current user, if author and current user are unassigned project members', async () => {
@@ -372,7 +372,9 @@ describe('User select dropdown', () => {
});
it('renders a list of found users and external participants matching search term', async () => {
- createComponent({ searchQueryHandler: jest.fn().mockResolvedValue(searchResponse) });
+ createComponent({
+ searchQueryHandler: jest.fn().mockResolvedValue(searchAutocompleteQueryResponse),
+ });
await waitForPromises();
findSearchField().vm.$emit('input', 'ro');
@@ -382,7 +384,9 @@ describe('User select dropdown', () => {
});
it('renders a list of found users only if no external participants match search term', async () => {
- createComponent({ searchQueryHandler: jest.fn().mockResolvedValue(searchResponse) });
+ createComponent({
+ searchQueryHandler: jest.fn().mockResolvedValue(searchAutocompleteQueryResponse),
+ });
await waitForPromises();
findSearchField().vm.$emit('input', 'roo');
@@ -392,8 +396,8 @@ describe('User select dropdown', () => {
});
it('shows a message about no matches if search returned an empty list', async () => {
- const responseCopy = cloneDeep(searchResponse);
- responseCopy.data.workspace.users.nodes = [];
+ const responseCopy = cloneDeep(searchAutocompleteQueryResponse);
+ responseCopy.data.workspace.users = [];
createComponent({
searchQueryHandler: jest.fn().mockResolvedValue(responseCopy),
diff --git a/spec/lib/gitlab/ci/components/instance_path_spec.rb b/spec/lib/gitlab/ci/components/instance_path_spec.rb
index 007c90d458e..97843781891 100644
--- a/spec/lib/gitlab/ci/components/instance_path_spec.rb
+++ b/spec/lib/gitlab/ci/components/instance_path_spec.rb
@@ -15,23 +15,24 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
describe 'FQDN path' do
let(:version) { 'master' }
+ let(:project_path) { project.full_path }
+ let(:address) { "acme.com/#{project_path}/secret-detection@#{version}" }
context 'when the project repository contains a templates directory' do
- let_it_be(:existing_project) do
+ let_it_be(:project) do
create(
:project, :custom_repo,
files: {
- 'templates/file.yml' => 'image: alpine_1',
- 'templates/dir/template.yml' => 'image: alpine_2'
+ 'templates/secret-detection.yml' => 'image: alpine_1',
+ 'templates/dast/template.yml' => 'image: alpine_2',
+ 'templates/dast/another-template.yml' => 'image: alpine_3',
+ 'templates/dast/another-folder/template.yml' => 'image: alpine_4'
}
)
end
- let(:project_path) { existing_project.full_path }
- let(:address) { "acme.com/#{project_path}/file@#{version}" }
-
before do
- existing_project.add_developer(user)
+ project.add_developer(user)
end
context 'when user does not have permissions' do
@@ -41,64 +42,50 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
end
- context 'when templates directory is top level' do
- it 'fetches the content' do
+ context 'when the component is simple (single file template)' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
+ expect(path.host).to eq(current_host)
+ expect(path.project_file_path).to eq('templates/secret-detection.yml')
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
+ end
- it 'provides the expected attributes', :aggregate_failures do
+ context 'when the component is complex (directory-based template)' do
+ let(:address) { "acme.com/#{project_path}/dast@#{version}" }
+
+ it 'fetches the component content', :aggregate_failures do
+ expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('file/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project_file_path).to eq('templates/dast/template.yml')
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
- context 'when file name is `template.yml`' do
- let(:address) { "acme.com/#{project_path}/dir@#{version}" }
+ context 'when there is an invalid nested component folder' do
+ let(:address) { "acme.com/#{project_path}/dast/another-folder@#{version}" }
- it 'fetches the content' do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
- expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('dir/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ it 'returns nil' do
+ expect(path.fetch_content!(current_user: user)).to be_nil
end
end
- end
- context 'when the project is nested under a subgroup' do
- let_it_be(:existing_group) { create(:group, :nested) }
- let_it_be(:existing_project) do
- create(
- :project, :custom_repo,
- files: {
- 'templates/file.yml' => 'image: alpine_1'
- },
- group: existing_group
- )
- end
-
- it 'fetches the content' do
- expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
- end
+ context 'when there is an invalid nested component path' do
+ let(:address) { "acme.com/#{project_path}/dast/another-template@#{version}" }
- it 'provides the expected attributes', :aggregate_failures do
- expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('file/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ it 'returns nil' do
+ expect(path.fetch_content!(current_user: user)).to be_nil
+ end
end
end
- context 'when fetching the latest version' do
- let_it_be(:existing_project) do
+ context 'when fetching the latest version of a component' do
+ let_it_be(:project) do
create(
:project, :custom_repo,
files: {
- 'templates/file.yml' => 'image: alpine_1'
+ 'templates/secret-detection.yml' => 'image: alpine_1'
}
)
end
@@ -106,30 +93,27 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:version) { '~latest' }
let(:latest_sha) do
- existing_project.repository.find_commits_by_message('Updates image').commits.last.sha
+ project.repository.commit('master').id
end
before do
- create(:release, project: existing_project, sha: existing_project.repository.root_ref_sha,
+ create(:release, project: project, sha: project.repository.root_ref_sha,
released_at: Time.zone.now - 1.day)
- existing_project.repository.update_file(
- user, 'templates/file.yml', 'image: alpine_2',
- message: 'Updates image', branch_name: existing_project.default_branch
+ project.repository.update_file(
+ user, 'templates/secret-detection.yml', 'image: alpine_2',
+ message: 'Updates image', branch_name: project.default_branch
)
- create(:release, project: existing_project, sha: latest_sha,
+ create(:release, project: project, sha: latest_sha,
released_at: Time.zone.now)
end
- it 'fetches the content' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('file/template.yml')
- expect(path.project).to eq(existing_project)
+ expect(path.project_file_path).to eq('templates/secret-detection.yml')
+ expect(path.project).to eq(project)
expect(path.sha).to eq(latest_sha)
end
end
@@ -137,31 +121,25 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
context 'when version does not exist' do
let(:version) { 'non-existent' }
- it 'returns nil when fetching the content' do
+ it 'returns nil', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to be_nil
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('file/template.yml')
- expect(path.project).to eq(existing_project)
+ expect(path.project_file_path).to be_nil
+ expect(path.project).to eq(project)
expect(path.sha).to be_nil
end
end
context 'when current GitLab instance is installed on a relative URL' do
- let(:address) { "acme.com/gitlab/#{project_path}/file@#{version}" }
+ let(:address) { "acme.com/gitlab/#{project_path}/secret-detection@#{version}" }
let(:current_host) { 'acme.com/gitlab/' }
- it 'fetches the content' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('file/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project_file_path).to eq('templates/secret-detection.yml')
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
end
end
@@ -169,10 +147,10 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
# All the following tests are for deprecated code and will be removed
# in https://gitlab.com/gitlab-org/gitlab/-/issues/415855
context 'when the project does not contain a templates directory' do
- let(:project_path) { existing_project.full_path }
+ let(:project_path) { project.full_path }
let(:address) { "acme.com/#{project_path}/component@#{version}" }
- let_it_be(:existing_project) do
+ let_it_be(:project) do
create(
:project, :custom_repo,
files: {
@@ -182,41 +160,35 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
end
before do
- existing_project.add_developer(user)
+ project.add_developer(user)
end
- it 'fetches the content' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
expect(path.project_file_path).to eq('component/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
context 'when project path is nested under a subgroup' do
- let_it_be(:existing_group) { create(:group, :nested) }
- let_it_be(:existing_project) do
+ let_it_be(:group) { create(:group, :nested) }
+ let_it_be(:project) do
create(
:project, :custom_repo,
files: {
'component/template.yml' => 'image: alpine'
},
- group: existing_group
+ group: group
)
end
- it 'fetches the content' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
expect(path.project_file_path).to eq('component/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
end
@@ -224,29 +196,23 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" }
let(:current_host) { 'acme.com/gitlab/' }
- it 'fetches the content' do
+ it 'fetches the component content', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
expect(path.project_file_path).to eq('component/template.yml')
- expect(path.project).to eq(existing_project)
- expect(path.sha).to eq(existing_project.commit('master').id)
+ expect(path.project).to eq(project)
+ expect(path.sha).to eq(project.commit('master').id)
end
end
context 'when version does not exist' do
let(:version) { 'non-existent' }
- it 'returns nil when fetching the content' do
+ it 'returns nil', :aggregate_failures do
expect(path.fetch_content!(current_user: user)).to be_nil
- end
-
- it 'provides the expected attributes', :aggregate_failures do
expect(path.host).to eq(current_host)
- expect(path.project_file_path).to eq('component/template.yml')
- expect(path.project).to eq(existing_project)
+ expect(path.project_file_path).to be_nil
+ expect(path.project).to eq(project)
expect(path.sha).to be_nil
end
end
diff --git a/spec/lib/gitlab/utils/markdown_spec.rb b/spec/lib/gitlab/utils/markdown_spec.rb
index 45953c7906e..d707cf51712 100644
--- a/spec/lib/gitlab/utils/markdown_spec.rb
+++ b/spec/lib/gitlab/utils/markdown_spec.rb
@@ -2,7 +2,7 @@
require 'fast_spec_helper'
-RSpec.describe Gitlab::Utils::Markdown do
+RSpec.describe Gitlab::Utils::Markdown, feature_category: :gitlab_docs do
let(:klass) do
Class.new do
include Gitlab::Utils::Markdown
@@ -53,25 +53,30 @@ RSpec.describe Gitlab::Utils::Markdown do
end
context 'when string has a product suffix' do
- %w[CORE STARTER PREMIUM ULTIMATE FREE BRONZE SILVER GOLD].each do |tier|
- ['', ' ONLY', ' SELF', ' SAAS'].each do |modifier|
- context "#{tier}#{modifier}" do
- let(:string) { "My Header (#{tier}#{modifier})" }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
- end
-
- context 'with "*" around a product suffix' do
- let(:string) { "My Header **(#{tier}#{modifier})**" }
-
- it 'ignores a product suffix' do
- is_expected.to eq 'my-header'
+ %w[PREMIUM ULTIMATE FREE].each do |tier|
+ [' ALL', ' SELF', ' SAAS'].each do |modifier|
+ ['', ' BETA', ' EXPERIMENT'].each do |status|
+ context "#{tier}#{modifier}#{status}" do
+ context 'with "*" around a product suffix' do
+ let(:string) { "My Header **(#{tier}#{modifier}#{status})**" }
+
+ it 'ignores a product suffix' do
+ is_expected.to eq 'my-header'
+ end
end
end
end
end
end
+ %w[BETA EXPERIMENT].each do |status|
+ context 'with "*" around a product suffix' do
+ let(:string) { "My Header **(#{status})**" }
+
+ it 'ignores a product suffix' do
+ is_expected.to eq 'my-header'
+ end
+ end
+ end
end
context 'when string is empty' do
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index a65dc6e0175..aebdcebbc5a 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -116,6 +116,17 @@ RSpec.describe API::Discussions, feature_category: :team_planning do
it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid'
it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid'
+ context "when position_type is file" do
+ it "creates a new diff note" do
+ position = diff_note.position.to_h.merge({ position_type: 'file' }).except(:ignore_whitespace_change)
+
+ post api("/projects/#{parent.id}/merge_requests/#{noteable['iid']}/discussions", user),
+ params: { body: 'hi!', position: position }
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
context "when position is for a previous commit on the merge request" do
it "returns a 400 bad request error because the line_code is old" do
# SHA taken from an earlier commit listed in spec/factories/merge_requests.rb
diff --git a/spec/services/ci/components/fetch_service_spec.rb b/spec/services/ci/components/fetch_service_spec.rb
index 532098b3b20..21b7df19f4a 100644
--- a/spec/services/ci/components/fetch_service_spec.rb
+++ b/spec/services/ci/components/fetch_service_spec.rb
@@ -3,15 +3,35 @@
require 'spec_helper'
RSpec.describe Ci::Components::FetchService, feature_category: :pipeline_composition do
- let_it_be(:project) { create(:project, :repository, create_tag: 'v1.0') }
let_it_be(:user) { create(:user) }
let_it_be(:current_user) { user }
let_it_be(:current_host) { Gitlab.config.gitlab.host }
+ let_it_be(:content) do
+ <<~COMPONENT
+ job:
+ script: echo
+ COMPONENT
+ end
let(:service) do
described_class.new(address: address, current_user: current_user)
end
+ let_it_be(:project) do
+ project = create(
+ :project, :custom_repo,
+ files: {
+ 'template.yml' => content,
+ 'my-component/template.yml' => content,
+ 'my-dir/my-component/template.yml' => content
+ }
+ )
+
+ project.repository.add_tag(project.creator, 'v0.1', project.repository.commit.sha)
+
+ project
+ end
+
before do
project.add_developer(user)
end
@@ -22,19 +42,6 @@ RSpec.describe Ci::Components::FetchService, feature_category: :pipeline_composi
shared_examples 'an external component' do
shared_examples 'component address' do
context 'when content exists' do
- let(:sha) { project.commit(version).id }
-
- let(:content) do
- <<~COMPONENT
- job:
- script: echo
- COMPONENT
- end
-
- before do
- stub_project_blob(sha, component_yaml_path, content)
- end
-
it 'returns the content' do
expect(result).to be_success
expect(result.payload[:content]).to eq(content)
@@ -42,6 +49,8 @@ RSpec.describe Ci::Components::FetchService, feature_category: :pipeline_composi
end
context 'when content does not exist' do
+ let(:address) { "#{current_host}/#{component_path}@~version-does-not-exist" }
+
it 'returns an error' do
expect(result).to be_error
expect(result.reason).to eq(:content_not_found)
diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb
index cabff88c83a..ebdce07d03c 100644
--- a/spec/services/groups/destroy_service_spec.rb
+++ b/spec/services/groups/destroy_service_spec.rb
@@ -69,10 +69,6 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do
end
it 'verifies that paths have been deleted' do
- Gitlab::GitalyClient::NamespaceService.allow do
- expect(Gitlab::GitalyClient::NamespaceService.new(project.repository_storage)
- .exists?(group.path)).to be_falsey
- end
expect(removed_repo).not_to exist
end
end
@@ -100,10 +96,6 @@ RSpec.describe Groups::DestroyService, feature_category: :groups_and_projects do
end
it 'verifies original paths and projects still exist' do
- Gitlab::GitalyClient::NamespaceService.allow do
- expect(Gitlab::GitalyClient::NamespaceService.new(project.repository_storage)
- .exists?(group.path)).to be_truthy
- end
expect(removed_repo).not_to exist
expect(Project.unscoped.count).to eq(1)
expect(Group.unscoped.count).to eq(2)
diff --git a/yarn.lock b/yarn.lock
index 1ac1e64833e..c50c418fc94 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1079,10 +1079,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
-"@eslint/js@^8.47.0":
- version "8.47.0"
- resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d"
- integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==
+"@eslint/js@8.48.0":
+ version "8.48.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb"
+ integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==
"@floating-ui/core@^1.2.6":
version "1.2.6"
@@ -5934,15 +5934,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
-eslint@8.47.0:
- version "8.47.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806"
- integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==
+eslint@8.48.0:
+ version "8.48.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
+ integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.2"
- "@eslint/js" "^8.47.0"
+ "@eslint/js" "8.48.0"
"@humanwhocodes/config-array" "^0.11.10"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"