summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-02 18:12:20 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-02 18:12:20 +0000
commit3c4d101de003ea292be5ac5baf5d73c6c2747367 (patch)
tree5918d12b69c4bfd84f05a585d62174586a80fd18
parentb2c21b99c7eb52b5fd906b1e7b4b08d4eb7a296c (diff)
downloadgitlab-ce-3c4d101de003ea292be5ac5baf5d73c6c2747367.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml4
-rw-r--r--CHANGELOG.md51
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue58
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note.vue39
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue88
-rw-r--r--app/assets/javascripts/design_management/pages/design/index.vue55
-rw-r--r--app/assets/javascripts/issues/show/components/title.vue1
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js15
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js12
-rw-r--r--app/assets/javascripts/profile/components/activity_calendar.vue100
-rw-r--r--app/assets/javascripts/profile/components/overview_tab.vue5
-rw-r--r--app/assets/javascripts/profile/components/profile_tabs.vue9
-rw-r--r--app/assets/javascripts/profile/constants.js7
-rw-r--r--app/assets/javascripts/profile/index.js6
-rw-r--r--app/assets/javascripts/profile/utils.js13
-rw-r--r--app/assets/javascripts/search/sidebar/components/app.vue7
-rw-r--r--app/assets/javascripts/search/sidebar/components/checkbox_filter.vue4
-rw-r--r--app/assets/javascripts/search/sidebar/components/language_filter.vue16
-rw-r--r--app/assets/javascripts/search/sidebar/components/scope_navigation.vue14
-rw-r--r--app/assets/javascripts/search/store/getters.js10
-rw-r--r--app/assets/javascripts/work_items/components/item_title.vue6
-rw-r--r--app/assets/stylesheets/page_bundles/project_quality.scss15
-rw-r--r--app/controllers/oauth/jira_dvcs/authorizations_controller.rb13
-rw-r--r--app/finders/notes_finder.rb2
-rw-r--r--app/graphql/mutations/release_asset_links/create.rb14
-rw-r--r--app/graphql/mutations/release_asset_links/delete.rb12
-rw-r--r--app/graphql/mutations/release_asset_links/update.rb12
-rw-r--r--app/helpers/users_helper.rb7
-rw-r--r--app/models/ci/build.rb10
-rw-r--r--app/models/commit_status.rb12
-rw-r--r--app/models/integration.rb2
-rw-r--r--app/models/integrations/datadog.rb1
-rw-r--r--app/models/integrations/prometheus.rb31
-rw-r--r--app/services/groups/transfer_service.rb2
-rw-r--r--app/services/releases/links/base_service.rb31
-rw-r--r--app/services/releases/links/create_service.rb25
-rw-r--r--app/services/releases/links/destroy_service.rb24
-rw-r--r--app/services/releases/links/update_service.rb24
-rw-r--r--app/services/resource_access_tokens/create_service.rb2
-rw-r--r--app/views/projects/pipelines/charts.html.haml1
-rw-r--r--app/views/users/show.html.haml90
-rw-r--r--config/application.rb1
-rw-r--r--config/feature_flags/development/code_basic_search_files_by_regexp.yml9
-rw-r--r--config/initializers/0_1_yaml_safe_load_file_patch.rb15
-rw-r--r--config/initializers/rest-client-hostname_override.rb2
-rw-r--r--danger/qa_selector/Dangerfile2
-rw-r--r--doc/administration/geo/replication/datatypes.md2
-rw-r--r--doc/administration/index.md2
-rw-r--r--doc/administration/instance_limits.md4
-rw-r--r--doc/administration/logs/index.md2
-rw-r--r--doc/administration/reference_architectures/10k_users.md6
-rw-r--r--doc/administration/reference_architectures/1k_users.md4
-rw-r--r--doc/administration/reference_architectures/25k_users.md6
-rw-r--r--doc/administration/reference_architectures/2k_users.md6
-rw-r--r--doc/administration/reference_architectures/3k_users.md6
-rw-r--r--doc/administration/reference_architectures/50k_users.md6
-rw-r--r--doc/administration/reference_architectures/5k_users.md6
-rw-r--r--doc/api/search.md12
-rw-r--r--doc/architecture/blueprints/search/code_search_with_zoekt.md2
-rw-r--r--doc/development/advanced_search.md2
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/changelog.md2
-rw-r--r--doc/development/documentation/feature_flags.md45
-rw-r--r--doc/development/documentation/styleguide/word_list.md12
-rw-r--r--doc/development/feature_development.md2
-rw-r--r--doc/development/fips_compliance.md2
-rw-r--r--doc/development/integrations/index.md9
-rw-r--r--doc/development/search/advanced_search_migration_styleguide.md18
-rw-r--r--doc/index.md2
-rw-r--r--doc/install/next_steps.md2
-rw-r--r--doc/integration/advanced_search/elasticsearch.md60
-rw-r--r--doc/integration/advanced_search/elasticsearch_troubleshooting.md14
-rw-r--r--doc/integration/index.md2
-rw-r--r--doc/subscriptions/bronze_starter.md4
-rw-r--r--doc/subscriptions/gitlab_dedicated/index.md18
-rw-r--r--doc/update/index.md14
-rw-r--r--doc/update/package/convert_to_ee.md2
-rw-r--r--doc/update/plan_your_upgrade.md2
-rw-r--r--doc/user/clusters/agent/install/index.md4
-rw-r--r--doc/user/project/import/github.md8
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md7
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md2
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md2
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md5
-rw-r--r--doc/user/project/pages/getting_started/pages_ci_cd_template.md2
-rw-r--r--doc/user/project/pages/getting_started/pages_forked_sample_project.md2
-rw-r--r--doc/user/project/pages/getting_started/pages_new_project_template.md2
-rw-r--r--doc/user/project/pages/getting_started/pages_ui.md2
-rw-r--r--doc/user/project/pages/getting_started_part_one.md2
-rw-r--r--doc/user/project/pages/introduction.md2
-rw-r--r--doc/user/project/pages/public_folder.md2
-rw-r--r--doc/user/project/pages/redirects.md2
-rw-r--r--doc/user/search/advanced_search.md18
-rw-r--r--doc/user/search/exact_code_search.md2
-rw-r--r--lib/api/commits.rb8
-rw-r--r--lib/api/entities/tag.rb8
-rw-r--r--lib/api/release/links.rb30
-rw-r--r--lib/api/tags.rb10
-rw-r--r--lib/banzai/filter/kroki_filter.rb6
-rw-r--r--lib/gitlab/file_finder.rb14
-rw-r--r--lib/gitlab/http_connection_adapter.rb4
-rw-r--r--lib/gitlab/octokit/middleware.rb7
-rw-r--r--lib/gitlab/url_blocker.rb2
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb31
-rw-r--r--spec/controllers/oauth/jira_dvcs/authorizations_controller_spec.rb40
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb4
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb46
-rw-r--r--spec/features/calendar_spec.rb325
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb125
-rw-r--r--spec/finders/notes_finder_spec.rb47
-rw-r--r--spec/finders/snippets_finder_spec.rb25
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/tag.json3
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js35
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js14
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js220
-rw-r--r--spec/frontend/design_management/mock_data/apollo_mock.js106
-rw-r--r--spec/frontend/design_management/mock_data/project.js17
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js92
-rw-r--r--spec/frontend/issues/show/components/title_spec.js93
-rw-r--r--spec/frontend/profile/components/activity_calendar_spec.js120
-rw-r--r--spec/frontend/profile/components/overview_tab_spec.js7
-rw-r--r--spec/frontend/profile/mock_data.js22
-rw-r--r--spec/frontend/profile/utils_spec.js15
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js6
-rw-r--r--spec/frontend/search/sidebar/components/checkbox_filter_spec.js2
-rw-r--r--spec/frontend/search/sidebar/components/language_filter_spec.js10
-rw-r--r--spec/frontend/search/sidebar/components/scope_navigation_spec.js35
-rw-r--r--spec/frontend/search/store/getters_spec.js16
-rw-r--r--spec/frontend/work_items/components/item_title_spec.js24
-rw-r--r--spec/graphql/mutations/release_asset_links/create_spec.rb2
-rw-r--r--spec/graphql/mutations/release_asset_links/delete_spec.rb14
-rw-r--r--spec/graphql/mutations/release_asset_links/update_spec.rb2
-rw-r--r--spec/helpers/users_helper_spec.rb15
-rw-r--r--spec/lib/banzai/filter/kroki_filter_spec.rb7
-rw-r--r--spec/lib/gitlab/ci/config/external/file/remote_spec.rb2
-rw-r--r--spec/lib/gitlab/file_finder_spec.rb126
-rw-r--r--spec/lib/gitlab/fogbugz_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/remote_stream_upload_spec.rb10
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb31
-rw-r--r--spec/lib/gitlab/prometheus/queries/validate_query_spec.rb5
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb11
-rw-r--r--spec/models/ci/build_spec.rb172
-rw-r--r--spec/models/commit_status_spec.rb23
-rw-r--r--spec/models/integration_spec.rb9
-rw-r--r--spec/models/integrations/datadog_spec.rb2
-rw-r--r--spec/models/integrations/prometheus_spec.rb55
-rw-r--r--spec/requests/api/ci/jobs_spec.rb40
-rw-r--r--spec/requests/api/commits_spec.rb26
-rw-r--r--spec/requests/api/release/links_spec.rb24
-rw-r--r--spec/requests/api/tags_spec.rb64
-rw-r--r--spec/services/releases/links/create_service_spec.rb82
-rw-r--r--spec/services/releases/links/destroy_service_spec.rb69
-rw-r--r--spec/services/releases/links/update_service_spec.rb87
-rw-r--r--spec/services/resource_access_tokens/create_service_spec.rb51
-rw-r--r--spec/support/shared_contexts/features/integrations/integrations_shared_context.rb1
-rw-r--r--spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb12
-rw-r--r--tooling/lib/tooling/find_codeowners.rb6
165 files changed, 2493 insertions, 1292 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dd23c648bd4..ff8994a9797 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -146,7 +146,7 @@ variables:
GET_SOURCES_ATTEMPTS: "3"
DEBIAN_VERSION: "bullseye"
CHROME_VERSION: "109"
- DOCKER_VERSION: "20.10.14"
+ DOCKER_VERSION: "23.0.1"
RUBY_VERSION: "2.7"
GO_VERSION: "1.18"
RUST_VERSION: "1.65"
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 0ee810340ff..5c7e78b0b67 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -353,7 +353,7 @@
.use-buildx:
extends: .use-docker-in-docker
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-slim:docker-${DOCKER_VERSION}-buildx-0.8
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-slim:docker-${DOCKER_VERSION}
variables:
QEMU_IMAGE: tonistiigi/binfmt:qemu-v7.0.0
before_script:
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index 369330f8189..7147b31150c 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -95,7 +95,7 @@ review-build-cng:
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
- GITLAB_HELM_CHART_REF: "afcef7854ac72c5ff958035ef210ba6c68ec800b" # 6.8.0: https://gitlab.com/gitlab-org/charts/gitlab/-/commit/afcef7854ac72c5ff958035ef210ba6c68ec800b
+ GITLAB_HELM_CHART_REF: "febc4ad69acb7bba0eeb4a62daa577d0b7c3ee71" # 6.9.1: https://gitlab.com/gitlab-org/charts/gitlab/-/commit/febc4ad69acb7bba0eeb4a62daa577d0b7c3ee71
environment:
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 6a05bd84830..6c4171da36e 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1359,6 +1359,8 @@
- !reference [".rails:rules:ee-and-foss-default-rules", rules]
- <<: *if-default-refs
changes: *backend-patterns
+ - <<: *if-default-refs
+ changes: *backstage-patterns
.rails:rules:ee-and-foss-unit:predictive:
rules:
@@ -1368,6 +1370,8 @@
- !reference [".rails:rules:unit-integration:predictive-default-rules", rules]
- <<: *if-merge-request
changes: *backend-patterns
+ - <<: *if-merge-request
+ changes: *backstage-patterns
.rails:rules:ee-and-foss-integration:
rules:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b82e5de350f..feaf0e9997a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,23 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 15.9.2 (2023-03-02)
+
+### Security (12 changes)
+
+- [Using builds metadata to determine debug_mode](gitlab-org/security/gitlab@e19fcea675071d005eb72c7e100ff0b357f43508) ([merge request](gitlab-org/security/gitlab!3022))
+- [Fix pagination limits for Commits API](gitlab-org/security/gitlab@f71e2650b44e306c8291a8fa5f8557ff4ae4f5d7) ([merge request](gitlab-org/security/gitlab!3071))
+- [Mask Google IAP account details in Prometheus integration](gitlab-org/security/gitlab@8cad41d16614f7eb6a0f1693046ae1981ff413d5) ([merge request](gitlab-org/security/gitlab!3081))
+- [Stop Group Transfer Service if SAML Provider or SCIM token is present](gitlab-org/security/gitlab@e7ebbc1d37372c147392a3854186f4bb7fd15db5) ([merge request](gitlab-org/security/gitlab!3095))
+- [Protect Datadog API key by changing Datadog site](gitlab-org/security/gitlab@abe3343d6cd0397a6b1b491878a9e8dfc5774a2f) ([merge request](gitlab-org/security/gitlab!3093))
+- [Protect integrations' sensitive information exposed via API](gitlab-org/security/gitlab@0036ee57dd9f37858ca09746be20fa254347a7ef) ([merge request](gitlab-org/security/gitlab!3087))
+- [Disallow maintainer to create an owner access token](gitlab-org/security/gitlab@820d02055d2a958462da3be5587d460a905d157f) ([merge request](gitlab-org/security/gitlab!3090))
+- [Paste only text content in work items title](gitlab-org/security/gitlab@5ef125158ceaf0220260423d67cf6a0e1c973e63) ([merge request](gitlab-org/security/gitlab!3074))
+- [Jira DVCS OAuth Open Redirect Vulnerability](gitlab-org/security/gitlab@d6295e117531bc9cde690ba49a456be6883fcd21) ([merge request](gitlab-org/security/gitlab!3077))
+- [Block private personal snippet from unauthorized users](gitlab-org/security/gitlab@1471002b48fba676367397bdffa63a1b50c375bd) ([merge request](gitlab-org/security/gitlab!3079))
+- [Verify Kroki diagram type](gitlab-org/security/gitlab@c76ccc6be3115ded496bbd1bde7da6e4a7dd19ba) ([merge request](gitlab-org/security/gitlab!3056))
+- [Check read_release permission before showing releases in Tags API](gitlab-org/security/gitlab@e176a4eb4d266cf774a06ff021c3789a2cb830d9) ([merge request](gitlab-org/security/gitlab!3060))
+
## 15.9.1 (2023-02-23)
### Fixed (2 changes)
@@ -732,6 +749,23 @@ entry.
- [Remove Gitlab::Redis::DuplicateJobs](gitlab-org/gitlab@73d863b0a49175cce7649c0936b2e16157f61665) ([merge request](gitlab-org/gitlab!109122))
- [Clean-up feature flag `hash_based_cache_for_protected_branches`](gitlab-org/gitlab@96e8a07564bac07a100556e00ce4af3f21dca293) ([merge request](gitlab-org/gitlab!108724))
+## 15.8.4 (2023-03-02)
+
+### Security (12 changes)
+
+- [Using builds metadata to determine debug_mode](gitlab-org/security/gitlab@169fdb3222a9701b5818ef7c00f8f292dc60495d) ([merge request](gitlab-org/security/gitlab!3035))
+- [Fix pagination limits for Commits API](gitlab-org/security/gitlab@3d58c0fef6429d1030d1dfce1ca523ef33a0054b) ([merge request](gitlab-org/security/gitlab!3072))
+- [Mask Google IAP account details in Prometheus integration](gitlab-org/security/gitlab@96426e4c799e9bf5e90e5e57b2e54235831819a3) ([merge request](gitlab-org/security/gitlab!3082))
+- [Stop Group Transfer Service if SAML Provider or SCIM token is present](gitlab-org/security/gitlab@9496a2ed22f73bf83e56b1ff502fefcfe777ad07) ([merge request](gitlab-org/security/gitlab!3097))
+- [Protect Datadog API key by changing Datadog site](gitlab-org/security/gitlab@c6804e50cb60fc4747ea573306eec17eb0dd25f9) ([merge request](gitlab-org/security/gitlab!3094))
+- [Protect integrations' sensitive information exposed via API](gitlab-org/security/gitlab@a408475163272b926e65b1cf56c9efde09eac8dd) ([merge request](gitlab-org/security/gitlab!3088))
+- [Disallow maintainer to create an owner access token](gitlab-org/security/gitlab@d184909f6ab9123a6131c5c37452ace5c4bc8d3d) ([merge request](gitlab-org/security/gitlab!3091))
+- [Paste only text content in work items title](gitlab-org/security/gitlab@d8c48ade46fd75ab62731fced05cdfa2451bcdfa) ([merge request](gitlab-org/security/gitlab!3075))
+- [Jira DVCS OAuth Open Redirect Vulnerability](gitlab-org/security/gitlab@91ee37eeaaae8cc6d923f6b4b28ce0d7914342dd) ([merge request](gitlab-org/security/gitlab!3063))
+- [Block private personal snippet from unauthorized users](gitlab-org/security/gitlab@d687866d69cbdf25a3ca7185974c02402345015d) ([merge request](gitlab-org/security/gitlab!3030))
+- [Verify Kroki diagram type](gitlab-org/security/gitlab@4ec26a4479e73233d0f77bc5a5e764d506c29faf) ([merge request](gitlab-org/security/gitlab!3055))
+- [Check read_release permission before showing releases in Tags API](gitlab-org/security/gitlab@32bf21efc32fcb6a3803993959b50d8a9cd07d25) ([merge request](gitlab-org/security/gitlab!3057))
+
## 15.8.3 (2023-02-15)
### Fixed (3 changes)
@@ -1226,6 +1260,23 @@ No changes.
- [Do not use _test when not necessary](gitlab-org/gitlab@1bde73aba2bd1d7f9e833c7325cffa0c90d1c106) ([merge request](gitlab-org/gitlab!107373))
- [Add config/redis.yml unified config file](gitlab-org/gitlab@ace8301236eecc07a511975b57f80e21ec7be3c2) ([merge request](gitlab-org/gitlab!106854))
+## 15.7.8 (2023-03-02)
+
+### Security (12 changes)
+
+- [Using builds metadata to determine debug_mode](gitlab-org/security/gitlab@12be0c159940a35899851f2867fde1237dae254b) ([merge request](gitlab-org/security/gitlab!3036))
+- [Fix pagination limits for Commits API](gitlab-org/security/gitlab@d507c5d906aff98a8bff943181299cbec5cc43db) ([merge request](gitlab-org/security/gitlab!3073))
+- [Mask Google IAP account details in Prometheus integration](gitlab-org/security/gitlab@54420f92a366e2a7648c10baaaf67492d6676746) ([merge request](gitlab-org/security/gitlab!3083))
+- [Stop Group Transfer Service if SAML Provider or SCIM token is present](gitlab-org/security/gitlab@52400160cd607fb30411dec04b516a1314e44996) ([merge request](gitlab-org/security/gitlab!3098))
+- [Protect Datadog API key by changing Datadog site](gitlab-org/security/gitlab@9aa3ba9f719a786238ae59914d5456666363940e) ([merge request](gitlab-org/security/gitlab!3096))
+- [Protect integrations' sensitive information exposed via API](gitlab-org/security/gitlab@60c22681f52c2aadcb55e1b9e92d358076e3c92c) ([merge request](gitlab-org/security/gitlab!3089))
+- [Disallow maintainer to create an owner access token](gitlab-org/security/gitlab@2adeb7fafb119a43c0bfe162fbc66d2740cb4168) ([merge request](gitlab-org/security/gitlab!3092))
+- [Paste only text content in work items title](gitlab-org/security/gitlab@5fa8a9bf683427af6f25e043b3f0a332719bc970) ([merge request](gitlab-org/security/gitlab!3076))
+- [Jira DVCS OAuth Open Redirect Vulnerability](gitlab-org/security/gitlab@3598b2558de92b0a775f09beb739c6e2f90ff7ab) ([merge request](gitlab-org/security/gitlab!3064))
+- [Block private personal snippet from unauthorized users](gitlab-org/security/gitlab@a106541570423480c9c510f512a2dc61acc5c01f) ([merge request](gitlab-org/security/gitlab!2994))
+- [Verify Kroki diagram type](gitlab-org/security/gitlab@eafe89b8be423e4828fe92769353b7f17ffe895e) ([merge request](gitlab-org/security/gitlab!3054))
+- [Check read_release permission before showing releases in Tags API](gitlab-org/security/gitlab@d56500c47754c7d5eb11f3c84bedbe60366eff0e) ([merge request](gitlab-org/security/gitlab!3058))
+
## 15.7.7 (2023-02-10)
No changes.
diff --git a/Gemfile b/Gemfile
index 9f43ca6daeb..290f762275d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -371,7 +371,7 @@ gem 'prometheus-client-mmap', '~> 0.17', require: 'prometheus/client'
gem 'warning', '~> 1.3.0'
group :development do
- gem 'lefthook', '~> 1.3.2', require: false
+ gem 'lefthook', '~> 1.3.3', require: false
gem 'rubocop'
gem 'solargraph', '~> 0.47.2', require: false
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 89e4073c128..e02aa509807 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -313,7 +313,7 @@
{"name":"kramdown","version":"2.3.2","platform":"ruby","checksum":"cb4530c2e9d16481591df2c9336723683c354e5416a5dd3e447fa48215a6a71c"},
{"name":"kramdown-parser-gfm","version":"1.1.0","platform":"ruby","checksum":"fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729"},
{"name":"launchy","version":"2.5.0","platform":"ruby","checksum":"954243c4255920982ce682f89a42e76372dba94770bf09c23a523e204bdebef5"},
-{"name":"lefthook","version":"1.3.2","platform":"ruby","checksum":"38607be9d670af5bfbbcb2159459f4403bc8e1b10885a923b9e512b3b72b3dec"},
+{"name":"lefthook","version":"1.3.3","platform":"ruby","checksum":"8269a799d0abad6aaf188edb66a661c729abe6b74f3d8d660529d51f9ed2dc5d"},
{"name":"letter_opener","version":"1.7.0","platform":"ruby","checksum":"095bc0d58e006e5b43ea7d219e64ecf2de8d1f7d9dafc432040a845cf59b4725"},
{"name":"letter_opener_web","version":"2.0.0","platform":"ruby","checksum":"33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f"},
{"name":"libyajl2","version":"1.2.0","platform":"ruby","checksum":"1117cd1e48db013b626e36269bbf1cef210538ca6d2e62d3fa3db9ded005b258"},
diff --git a/Gemfile.lock b/Gemfile.lock
index fe25496d125..e1953b90a38 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -836,7 +836,7 @@ GEM
kramdown (~> 2.0)
launchy (2.5.0)
addressable (~> 2.7)
- lefthook (1.3.2)
+ lefthook (1.3.3)
letter_opener (1.7.0)
launchy (~> 2.2)
letter_opener_web (2.0.0)
@@ -1727,7 +1727,7 @@ DEPENDENCIES
knapsack (~> 1.21.1)
kramdown (~> 2.3.1)
kubeclient (~> 4.9.3)!
- lefthook (~> 1.3.2)
+ lefthook (~> 1.3.3)
letter_opener_web (~> 2.0.0)
license_finder (~> 7.0)
licensee (~> 9.15)
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
index 7083c5cd0b7..cd372374d83 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlLink, GlTooltipDirective } from '@gitlab/ui';
-import { ApolloMutation } from 'vue-apollo';
import * as Sentry from '@sentry/browser';
import { createAlert } from '~/flash';
import { __, s__ } from '~/locale';
@@ -35,7 +34,6 @@ export default {
},
},
components: {
- ApolloMutation,
DesignNote,
DesignNotePin,
DesignNoteSignedOut,
@@ -108,7 +106,6 @@ export default {
},
data() {
return {
- discussionComment: '',
isFormRendered: false,
activeDiscussion: {},
noteToDelete: null,
@@ -119,10 +116,9 @@ export default {
};
},
computed: {
- mutationPayload() {
+ mutationVariables() {
return {
noteableId: this.noteableId,
- body: this.discussionComment,
discussionId: this.discussion.id,
};
},
@@ -168,9 +164,15 @@ export default {
onDone({ data: { createNote } }) {
if (hasErrors(createNote)) {
createAlert({ message: ADD_DISCUSSION_COMMENT_ERROR });
+ } else {
+ /**
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/388314
+ *
+ * Hide the form once the create note mutation is completed.
+ */
+ this.hideForm();
}
- this.discussionComment = '';
- this.hideForm();
+
if (this.shouldChangeResolvedStatus) {
this.toggleResolvedStatus();
}
@@ -180,7 +182,6 @@ export default {
},
hideForm() {
this.isFormRendered = false;
- this.discussionComment = '';
},
showForm() {
this.$emit('open-form', this.discussion.id);
@@ -362,33 +363,24 @@ export default {
:placeholder-text="__('Reply…')"
@focus="showForm"
/>
- <apollo-mutation
+ <design-reply-form
v-else
- #default="{ mutate, loading }"
- :mutation="$options.createNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- @done="onDone"
- @error="onCreateNoteError"
+ :design-note-mutation="$options.createNoteMutation"
+ :mutation-variables="mutationVariables"
+ :markdown-preview-path="markdownPreviewPath"
+ :noteable-id="noteableId"
+ :discussion-id="discussion.id"
+ @note-submit-complete="onDone"
+ @note-submit-failure="onCreateNoteError"
+ @cancel-form="hideForm"
>
- <design-reply-form
- v-model="discussionComment"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- :noteable-id="noteableId"
- :discussion-id="discussion.id"
- @submit-form="mutate"
- @cancel-form="hideForm"
- >
- <template v-if="discussion.resolvable" #resolve-checkbox>
- <label data-testid="resolve-checkbox">
- <input v-model="shouldChangeResolvedStatus" type="checkbox" />
- {{ resolveCheckboxText }}
- </label>
- </template>
- </design-reply-form>
- </apollo-mutation>
+ <template v-if="discussion.resolvable" #resolve-checkbox>
+ <label data-testid="resolve-checkbox">
+ <input v-model="shouldChangeResolvedStatus" type="checkbox" />
+ {{ resolveCheckboxText }}
+ </label>
+ </template>
+ </design-reply-form>
</template>
</li>
</ul>
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note.vue b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
index c1207ad527e..3e6c49777ca 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_note.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note.vue
@@ -8,7 +8,6 @@ import {
GlLink,
GlTooltipDirective,
} from '@gitlab/ui';
-import { ApolloMutation } from 'vue-apollo';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
@@ -26,7 +25,6 @@ export default {
deleteCommentText: __('Delete comment'),
},
components: {
- ApolloMutation,
DesignReplyForm,
GlAvatar,
GlAvatarLink,
@@ -58,7 +56,6 @@ export default {
},
data() {
return {
- noteText: this.note.body,
isEditing: false,
isError: true,
};
@@ -76,10 +73,9 @@ export default {
isNoteLinked() {
return extractDesignNoteId(this.$route.hash) === this.noteAnchorId;
},
- mutationPayload() {
+ mutationVariables() {
return {
id: this.note.id,
- body: this.noteText,
};
},
isEditButtonVisible() {
@@ -95,7 +91,6 @@ export default {
methods: {
hideForm() {
this.isEditing = false;
- this.noteText = this.note.body;
},
onDone({ data }) {
this.hideForm();
@@ -186,26 +181,18 @@ export default {
></div>
<slot name="resolved-status"></slot>
</template>
- <apollo-mutation
+ <design-reply-form
v-else
- #default="{ mutate, loading }"
- :mutation="$options.updateNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- @error="$emit('error', $event)"
- @done="onDone"
- >
- <design-reply-form
- v-model="noteText"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- :is-new-comment="false"
- :noteable-id="noteableId"
- class="gl-mt-5"
- @submit-form="mutate"
- @cancel-form="hideForm"
- />
- </apollo-mutation>
+ :markdown-preview-path="markdownPreviewPath"
+ :design-note-mutation="$options.updateNoteMutation"
+ :mutation-variables="mutationVariables"
+ :value="note.body"
+ :is-new-comment="false"
+ :noteable-id="noteableId"
+ class="gl-mt-5"
+ @note-submit-failure="$emit('error', $event)"
+ @note-submit-complete="onDone"
+ @cancel-form="hideForm"
+ />
</timeline-entry-item>
</template>
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
index 830f16b50ee..bf88e037813 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_reply_form.vue
@@ -25,19 +25,20 @@ export default {
GlButton,
},
props: {
+ designNoteMutation: {
+ type: Object,
+ required: true,
+ },
+ mutationVariables: {
+ type: Object,
+ required: false,
+ default: null,
+ },
markdownPreviewPath: {
type: String,
required: false,
default: '',
},
- value: {
- type: String,
- required: true,
- },
- isSaving: {
- type: Boolean,
- required: true,
- },
isNewComment: {
type: Boolean,
required: false,
@@ -52,16 +53,23 @@ export default {
required: false,
default: 'new',
},
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
- formText: this.value,
+ noteText: this.value,
+ saving: false,
+ noteUpdateDirty: false,
isLoggedIn: isLoggedIn(),
};
},
computed: {
hasValue() {
- return this.value.trim().length > 0;
+ return this.noteText.length > 0;
},
buttonText() {
return this.isNewComment
@@ -75,18 +83,63 @@ export default {
mounted() {
this.focusInput();
},
+ beforeDestroy() {
+ /**
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/388314
+ * Reply form closes and component destroys
+ * only when comment submission was successful,
+ * so we're safe to clear autosave data here conditionally.
+ */
+ this.$nextTick(() => {
+ if (!this.noteUpdateDirty) {
+ this.autosaveDiscussion.reset();
+ }
+ });
+ },
methods: {
+ handleInput() {
+ /**
+ * While the form is saving using ctrl+enter
+ * Do not mark it as dirty.
+ *
+ */
+ if (!this.saving) {
+ this.noteUpdateDirty = true;
+ }
+ },
submitForm() {
if (this.hasValue) {
- this.$emit('submit-form');
- this.autosaveDiscussion.reset();
+ this.saving = true;
+ this.$apollo
+ .mutate({
+ mutation: this.designNoteMutation,
+ variables: {
+ input: {
+ ...this.mutationVariables,
+ body: this.noteText,
+ },
+ },
+ update: () => {
+ this.noteUpdateDirty = false;
+ },
+ })
+ .then((response) => {
+ this.$emit('note-submit-complete', response);
+ })
+ .catch((errors) => {
+ this.$emit('note-submit-failure', errors);
+ })
+ .finally(() => {
+ this.saving = false;
+ });
}
},
cancelComment() {
- if (this.hasValue && this.formText !== this.value) {
+ if (this.hasValue && this.noteUpdateDirty) {
this.confirmCancelCommentModal();
} else {
this.$emit('cancel-form');
+ this.noteUpdateDirty = false;
}
},
async confirmCancelCommentModal() {
@@ -133,21 +186,21 @@ export default {
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:enable-autocomplete="true"
- :textarea-value="value"
+ :textarea-value="noteText"
:markdown-docs-path="$options.markdownDocsPath"
class="bordered-box"
>
<template #textarea>
<textarea
ref="textarea"
- :value="value"
+ v-model.trim="noteText"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="false"
data-qa-selector="note_textarea"
:aria-label="__('Description')"
:placeholder="__('Write a comment…')"
- @input="$emit('input', $event.target.value)"
+ @input="handleInput"
@keydown.meta.enter="submitForm"
@keydown.ctrl.enter="submitForm"
@keyup.esc.stop="cancelComment"
@@ -159,7 +212,8 @@ export default {
<div class="note-form-actions gl-display-flex">
<gl-button
ref="submitButton"
- :disabled="!hasValue || isSaving"
+ :disabled="!hasValue"
+ :loading="saving"
class="gl-mr-3 gl-w-auto!"
category="primary"
variant="confirm"
diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue
index 3a1c8ae43c5..2780ae70cc7 100644
--- a/app/assets/javascripts/design_management/pages/design/index.vue
+++ b/app/assets/javascripts/design_management/pages/design/index.vue
@@ -2,7 +2,6 @@
import { GlAlert } from '@gitlab/ui';
import { isNull } from 'lodash';
import Mousetrap from 'mousetrap';
-import { ApolloMutation } from 'vue-apollo';
import { keysFor, ISSUE_CLOSE_DESIGN } from '~/behaviors/shortcuts/keybindings';
import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
@@ -51,7 +50,6 @@ const DEFAULT_MAX_SCALE = 2;
export default {
components: {
- ApolloMutation,
DesignReplyForm,
DesignPresentation,
DesignScaler,
@@ -91,7 +89,6 @@ export default {
data() {
return {
design: {},
- comment: '',
annotationCoordinates: null,
errorMessage: '',
scale: DEFAULT_SCALE,
@@ -130,9 +127,6 @@ export default {
markdownPreviewPath() {
return `/${this.projectPath}/preview_markdown?target_type=Issue`;
},
- isSubmitButtonDisabled() {
- return this.comment.trim().length === 0;
- },
designVariables() {
return {
fullPath: this.projectPath,
@@ -141,11 +135,10 @@ export default {
atVersion: this.designsVersion,
};
},
- mutationPayload() {
+ mutationVariables() {
const { x, y, width, height } = this.annotationCoordinates;
return {
noteableId: this.design.id,
- body: this.comment,
position: {
headSha: this.design.diffRefs.headSha,
baseSha: this.design.diffRefs.baseSha,
@@ -197,13 +190,23 @@ export default {
Mousetrap.unbind(keysFor(ISSUE_CLOSE_DESIGN));
},
methods: {
- addImageDiffNoteToStore(store, { data: { createImageDiffNote } }) {
+ addImageDiffNoteToStore({ data }) {
+ const { createImageDiffNote } = data;
+ /**
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/388314
+ *
+ * The getClient method is not documented. In future,
+ * need to check for any alternative.
+ */
+ const { cache } = this.$apollo.getClient();
+
updateStoreAfterAddImageDiffNote(
- store,
+ cache,
createImageDiffNote,
getDesignQuery,
this.designVariables,
);
+ this.closeCommentForm(data);
},
updateImageDiffNoteInStore(store, { data: { repositionImageDiffNote } }) {
return updateStoreAfterRepositionImageDiffNote(
@@ -289,7 +292,6 @@ export default {
}
},
closeCommentForm(data) {
- this.comment = '';
this.annotationCoordinates = null;
if (data?.data && !isNull(this.prevCurrentUserTodos)) {
@@ -407,27 +409,18 @@ export default {
@todoError="onTodoError"
>
<template #reply-form>
- <apollo-mutation
+ <design-reply-form
v-if="isAnnotating"
- #default="{ mutate, loading }"
- :mutation="$options.createImageDiffNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- :update="addImageDiffNoteToStore"
- @done="closeCommentForm"
- @error="onCreateImageDiffNoteError"
- >
- <design-reply-form
- ref="newDiscussionForm"
- v-model="comment"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- :noteable-id="design.id"
- @submit-form="mutate"
- @cancel-form="closeCommentForm"
- /> </apollo-mutation
- ></template>
+ ref="newDiscussionForm"
+ :design-note-mutation="$options.createImageDiffNoteMutation"
+ :mutation-variables="mutationVariables"
+ :markdown-preview-path="markdownPreviewPath"
+ :noteable-id="design.id"
+ @note-submit-complete="addImageDiffNoteToStore"
+ @note-submit-failure="onCreateImageDiffNoteError"
+ @cancel-form="closeCommentForm"
+ />
+ </template>
</design-sidebar>
</div>
</template>
diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue
index 6978f730e1d..58eaa0331e5 100644
--- a/app/assets/javascripts/issues/show/components/title.vue
+++ b/app/assets/javascripts/issues/show/components/title.vue
@@ -77,6 +77,7 @@ export default {
}"
class="title gl-font-size-h-display"
data-qa-selector="title_content"
+ data-testid="issue-title"
dir="auto"
></h1>
<gl-button
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index fb761725c43..6f7ce7d59c3 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -58,7 +58,7 @@ export const getLevelFromContributions = (count) => {
};
export default class ActivityCalendar {
- constructor(
+ constructor({
container,
activitiesContainer,
timestamps,
@@ -66,7 +66,8 @@ export default class ActivityCalendar {
utcOffset = 0,
firstDayOfWeek = firstDayOfWeekChoices.sunday,
monthsAgo = 12,
- ) {
+ onClickDay,
+ }) {
this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
@@ -91,6 +92,7 @@ export default class ActivityCalendar {
this.firstDayOfWeek = firstDayOfWeek;
this.activitiesContainer = activitiesContainer;
this.container = container;
+ this.onClickDay = onClickDay;
// Loop through the timestamps to create a group of objects
// The group of objects will be grouped based on the day of the week they are
@@ -152,7 +154,8 @@ export default class ActivityCalendar {
.append('svg')
.attr('width', width)
.attr('height', 169)
- .attr('class', 'contrib-calendar');
+ .attr('class', 'contrib-calendar')
+ .attr('data-testid', 'contrib-calendar');
}
dayYPos(day) {
@@ -281,6 +284,12 @@ export default class ActivityCalendar {
this.currentSelectedDate.getDate(),
].join('-');
+ if (this.onClickDay) {
+ this.onClickDay(date);
+
+ return;
+ }
+
$(this.activitiesContainer)
.empty()
.append(loadingIconForLegacyJS({ size: 'lg' }));
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 90eafa85886..3a23f9ff49c 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -247,15 +247,15 @@ export default class UserTabs {
$calendarWrap.find('.calendar-hint').text(calendarHint);
// eslint-disable-next-line no-new
- new ActivityCalendar(
- '.tab-pane.active .js-contrib-calendar',
- '.tab-pane.active .user-calendar-activities',
- data,
+ new ActivityCalendar({
+ container: '.tab-pane.active .js-contrib-calendar',
+ activitiesContainer: '.tab-pane.active .user-calendar-activities',
+ timestamps: data,
calendarActivitiesPath,
utcOffset,
- gon.first_day_of_week,
+ firstDayOfTheWeek: gon.first_day_of_week,
monthsAgo,
- );
+ });
}
toggleLoading(status) {
diff --git a/app/assets/javascripts/profile/components/activity_calendar.vue b/app/assets/javascripts/profile/components/activity_calendar.vue
new file mode 100644
index 00000000000..d359b478d35
--- /dev/null
+++ b/app/assets/javascripts/profile/components/activity_calendar.vue
@@ -0,0 +1,100 @@
+<script>
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { debounce } from 'lodash';
+
+import { __ } from '~/locale';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import ActivityCalendar from '~/pages/users/activity_calendar';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import { getVisibleCalendarPeriod } from '../utils';
+
+export default {
+ i18n: {
+ errorAlertTitle: __('There was an error loading users activity calendar.'),
+ retry: __('Retry'),
+ calendarHint: __('Issues, merge requests, pushes, and comments.'),
+ },
+ components: { GlLoadingIcon, GlAlert },
+ inject: ['userCalendarPath', 'utcOffset'],
+ data() {
+ return {
+ isLoading: true,
+ showCalendar: true,
+ hasError: false,
+ };
+ },
+ mounted() {
+ this.renderActivityCalendar();
+ window.addEventListener('resize', this.handleResize);
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.handleResize);
+ },
+ methods: {
+ async renderActivityCalendar() {
+ if (bp.getBreakpointSize() === 'xs') {
+ this.showCalendar = false;
+
+ return;
+ }
+
+ this.showCalendar = true;
+ this.isLoading = true;
+ this.hasError = false;
+
+ try {
+ const data = await AjaxCache.retrieve(this.userCalendarPath);
+
+ this.isLoading = false;
+
+ // Wait for `calendarContainer` to render
+ await this.$nextTick();
+ const monthsAgo = getVisibleCalendarPeriod(this.$refs.calendarContainer);
+
+ // eslint-disable-next-line no-new
+ new ActivityCalendar({
+ container: this.$refs.calendarSvgContainer,
+ timestamps: data,
+ utcOffset: this.utcOffset,
+ firstDayOfWeek: gon.first_day_of_week,
+ monthsAgo,
+ onClickDay: this.handleClickDay,
+ });
+ } catch {
+ this.isLoading = false;
+ this.hasError = true;
+ }
+ },
+ handleResize: debounce(function debouncedHandleResize() {
+ this.renderActivityCalendar();
+ }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
+ handleClickDay() {
+ // Render activities for specific day.
+ // Blocked by https://gitlab.com/gitlab-org/gitlab/-/issues/378695
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="showCalendar" ref="calendarContainer">
+ <gl-loading-icon v-if="isLoading" size="md" />
+ <gl-alert
+ v-else-if="hasError"
+ :title="$options.i18n.errorAlertTitle"
+ :dismissible="false"
+ variant="danger"
+ :primary-button-text="$options.i18n.retry"
+ @primaryAction="renderActivityCalendar"
+ />
+ <div v-else class="gl-text-center">
+ <div class="gl-display-inline-block gl-relative">
+ <div ref="calendarSvgContainer"></div>
+ <p class="gl-absolute gl-right-0 gl-bottom-0 gl-mb-0 gl-font-sm">
+ {{ $options.i18n.calendarHint }}
+ </p>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/profile/components/overview_tab.vue b/app/assets/javascripts/profile/components/overview_tab.vue
index e884c2d7083..76fb13919df 100644
--- a/app/assets/javascripts/profile/components/overview_tab.vue
+++ b/app/assets/javascripts/profile/components/overview_tab.vue
@@ -1,17 +1,18 @@
<script>
import { GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
+import ActivityCalendar from './activity_calendar.vue';
export default {
i18n: {
title: s__('UserProfile|Overview'),
},
- components: { GlTab },
+ components: { GlTab, ActivityCalendar },
};
</script>
<template>
<gl-tab :title="$options.i18n.title">
- <!-- placeholder -->
+ <activity-calendar />
</gl-tab>
</template>
diff --git a/app/assets/javascripts/profile/components/profile_tabs.vue b/app/assets/javascripts/profile/components/profile_tabs.vue
index 2425d56c52a..b39bfabb832 100644
--- a/app/assets/javascripts/profile/components/profile_tabs.vue
+++ b/app/assets/javascripts/profile/components/profile_tabs.vue
@@ -66,7 +66,12 @@ export default {
</script>
<template>
- <gl-tabs>
- <component :is="component" v-for="{ key, component } in $options.tabs" :key="key" />
+ <gl-tabs nav-class="gl-bg-gray-10" align="center">
+ <component
+ :is="component"
+ v-for="{ key, component } in $options.tabs"
+ :key="key"
+ class="container-fluid container-limited"
+ />
</gl-tabs>
</template>
diff --git a/app/assets/javascripts/profile/constants.js b/app/assets/javascripts/profile/constants.js
new file mode 100644
index 00000000000..e19994c6784
--- /dev/null
+++ b/app/assets/javascripts/profile/constants.js
@@ -0,0 +1,7 @@
+export const CALENDAR_PERIOD_6_MONTHS = 6;
+export const CALENDAR_PERIOD_12_MONTHS = 12;
+/* computation based on
+ * width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
+ * (see activity_calendar.js)
+ */
+export const OVERVIEW_CALENDAR_BREAKPOINT = 918;
diff --git a/app/assets/javascripts/profile/index.js b/app/assets/javascripts/profile/index.js
index 5378ed3d743..a89331cda96 100644
--- a/app/assets/javascripts/profile/index.js
+++ b/app/assets/javascripts/profile/index.js
@@ -7,8 +7,14 @@ export const initProfileTabs = () => {
if (!el) return false;
+ const { userCalendarPath, utcOffset } = el.dataset;
+
return new Vue({
el,
+ provide: {
+ userCalendarPath,
+ utcOffset,
+ },
render(createElement) {
return createElement(ProfileTabs);
},
diff --git a/app/assets/javascripts/profile/utils.js b/app/assets/javascripts/profile/utils.js
new file mode 100644
index 00000000000..5b757120544
--- /dev/null
+++ b/app/assets/javascripts/profile/utils.js
@@ -0,0 +1,13 @@
+import {
+ OVERVIEW_CALENDAR_BREAKPOINT,
+ CALENDAR_PERIOD_6_MONTHS,
+ CALENDAR_PERIOD_12_MONTHS,
+} from './constants';
+
+export const getVisibleCalendarPeriod = (calendarContainer) => {
+ const { width } = calendarContainer.getBoundingClientRect();
+
+ return width < OVERVIEW_CALENDAR_BREAKPOINT
+ ? CALENDAR_PERIOD_6_MONTHS
+ : CALENDAR_PERIOD_12_MONTHS;
+};
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue
index 7c9e30a2d97..928cd46c9eb 100644
--- a/app/assets/javascripts/search/sidebar/components/app.vue
+++ b/app/assets/javascripts/search/sidebar/components/app.vue
@@ -1,5 +1,5 @@
<script>
-import { mapState } from 'vuex';
+import { mapState, mapGetters } from 'vuex';
import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SCOPE_ISSUES, SCOPE_MERGE_REQUESTS, SCOPE_BLOB } from '../constants';
@@ -16,11 +16,12 @@ export default {
mixins: [glFeatureFlagsMixin()],
computed: {
...mapState(['urlQuery']),
+ ...mapGetters(['currentScope']),
showIssueAndMergeFilters() {
- return this.urlQuery.scope === SCOPE_ISSUES || this.urlQuery.scope === SCOPE_MERGE_REQUESTS;
+ return this.currentScope === SCOPE_ISSUES || this.currentScope === SCOPE_MERGE_REQUESTS;
},
showBlobFilter() {
- return this.urlQuery.scope === SCOPE_BLOB && this.glFeatures.searchBlobsLanguageAggregation;
+ return this.currentScope === SCOPE_BLOB && this.glFeatures.searchBlobsLanguageAggregation;
},
},
};
diff --git a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue
index 36cbe9ce693..3784f08adcc 100644
--- a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue
@@ -19,7 +19,7 @@ export default {
},
computed: {
...mapState(['query']),
- ...mapGetters(['queryLangugageFilters']),
+ ...mapGetters(['queryLanguageFilters']),
scope() {
return this.query.scope;
},
@@ -31,7 +31,7 @@ export default {
},
selectedFilter: {
get() {
- return intersection(this.flatDataFilterValues, this.queryLangugageFilters);
+ return intersection(this.flatDataFilterValues, this.queryLanguageFilters);
},
set(value) {
this.setQuery({ key: this.filtersData?.filterParam, value });
diff --git a/app/assets/javascripts/search/sidebar/components/language_filter.vue b/app/assets/javascripts/search/sidebar/components/language_filter.vue
index e5306cd7b79..b2f8d3e1f5f 100644
--- a/app/assets/javascripts/search/sidebar/components/language_filter.vue
+++ b/app/assets/javascripts/search/sidebar/components/language_filter.vue
@@ -32,19 +32,19 @@ export default {
computed: {
...mapState(['aggregations', 'sidebarDirty']),
...mapGetters([
- 'langugageAggregationBuckets',
+ 'languageAggregationBuckets',
'currentUrlQueryHasLanguageFilters',
- 'queryLangugageFilters',
+ 'queryLanguageFilters',
]),
hasBuckets() {
- return this.langugageAggregationBuckets.length > 0;
+ return this.languageAggregationBuckets.length > 0;
},
filtersData() {
return convertFiltersData(this.shortenedLanguageFilters);
},
shortenedLanguageFilters() {
if (!this.hasShowMore) {
- return this.langugageAggregationBuckets;
+ return this.languageAggregationBuckets;
}
if (this.showAll) {
return this.trimBuckets(MAX_ITEM_LENGTH);
@@ -52,16 +52,16 @@ export default {
return this.trimBuckets(DEFAULT_ITEM_LENGTH);
},
hasShowMore() {
- return this.langugageAggregationBuckets.length > DEFAULT_ITEM_LENGTH;
+ return this.languageAggregationBuckets.length > DEFAULT_ITEM_LENGTH;
},
hasOverMax() {
- return this.langugageAggregationBuckets.length > MAX_ITEM_LENGTH;
+ return this.languageAggregationBuckets.length > MAX_ITEM_LENGTH;
},
dividerClasses() {
return [...HR_DEFAULT_CLASSES, ...ONLY_SHOW_MD];
},
hasQueryFilters() {
- return this.queryLangugageFilters.length > 0;
+ return this.queryLanguageFilters.length > 0;
},
},
async created() {
@@ -78,7 +78,7 @@ export default {
this.showAll = true;
},
trimBuckets(length) {
- return this.langugageAggregationBuckets.slice(0, length);
+ return this.languageAggregationBuckets.slice(0, length);
},
cleanResetFilters() {
if (this.currentUrlQueryHasLanguageFilters) {
diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
index 5863381e2ef..02a3870f499 100644
--- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
+++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
@@ -44,9 +44,6 @@ export default {
isHighlighted ? 'gl-text-gray-900' : 'gl-text-gray-500',
];
},
- isActive(scope, index) {
- return this.urlQuery.scope ? this.urlQuery.scope === scope : index === 0;
- },
qaSelectorValue(item) {
return `${slugifyWithUnderscore(item.label)}_tab`;
},
@@ -60,16 +57,17 @@ export default {
<nav data-testid="search-filter">
<gl-nav vertical pills>
<gl-nav-item
- v-for="(item, scope, index) in navigation"
+ v-for="(item, scope) in navigation"
:key="scope"
- :link-classes="linkClasses(isActive(scope, index))"
+ :link-classes="linkClasses(item.active)"
class="gl-mb-1"
:href="item.link"
- :active="isActive(scope, index)"
+ :active="item.active"
:data-qa-selector="qaSelectorValue(item)"
+ :data-testid="qaSelectorValue(item)"
@click="handleClick(scope)"
- ><span>{{ item.label }}</span
- ><span v-if="item.count" :class="countClasses(isActive(scope, index))">
+ ><span data-testid="label">{{ item.label }}</span
+ ><span v-if="item.count" data-testid="count" :class="countClasses(item.active)">
{{ showFormatedCount(item.count)
}}<gl-icon
v-if="isCountOverLimit(item.count)"
diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js
index 853c83b07ee..36d98233e28 100644
--- a/app/assets/javascripts/search/store/getters.js
+++ b/app/assets/javascripts/search/store/getters.js
@@ -1,4 +1,4 @@
-import { has } from 'lodash';
+import { findKey, has } from 'lodash';
import { languageFilterData } from '~/search/sidebar/constants/language_filter_data';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
@@ -11,7 +11,7 @@ export const frequentProjects = (state) => {
return state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY];
};
-export const langugageAggregationBuckets = (state) => {
+export const languageAggregationBuckets = (state) => {
return (
state.aggregations.data.find(
(aggregation) => aggregation.name === languageFilterData.filterParam,
@@ -19,9 +19,9 @@ export const langugageAggregationBuckets = (state) => {
);
};
-export const queryLangugageFilters = (state) => {
- return state.query[languageFilterData.filterParam] || [];
-};
+export const currentScope = (state) => findKey(state.navigation, { active: true });
+
+export const queryLanguageFilters = (state) => state.query[languageFilterData.filterParam] || [];
export const currentUrlQueryHasLanguageFilters = (state) =>
has(state.urlQuery, languageFilterData.filterParam) &&
diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue
index 6aa3c54705c..1c0fed2dde9 100644
--- a/app/assets/javascripts/work_items/components/item_title.vue
+++ b/app/assets/javascripts/work_items/components/item_title.vue
@@ -29,6 +29,11 @@ export default {
handleSubmit() {
this.$refs.titleEl.blur();
},
+ handlePaste(e) {
+ e.preventDefault();
+ const text = e.clipboardData.getData('text');
+ this.$refs.titleEl.innerText = text;
+ },
},
};
</script>
@@ -48,6 +53,7 @@ export default {
:contenteditable="!disabled"
class="gl-px-4 gl-py-3 gl-ml-n4 gl-border gl-border-white gl-rounded-base gl-display-block"
:class="{ 'gl-hover-border-gray-200 gl-pseudo-placeholder': !disabled }"
+ @paste="handlePaste"
@blur="handleBlur"
@keyup="handleInput"
@keydown.enter.exact="handleSubmit"
diff --git a/app/assets/stylesheets/page_bundles/project_quality.scss b/app/assets/stylesheets/page_bundles/project_quality.scss
deleted file mode 100644
index 425b9544235..00000000000
--- a/app/assets/stylesheets/page_bundles/project_quality.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-@import 'page_bundles/mixins_and_variables_and_functions';
-
-/*
-When the single-stat component of gitlab UI adds the props of the title icon color,
-remove this file and use the props of gitlab UI to set the color
-Gitlab UI issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2157
-*/
-
-.code-quality-blocker .gl-icon {
- @include gl-text-red-800;
-}
-
-.code-quality-critical .gl-icon {
- @include gl-text-red-600;
-}
diff --git a/app/controllers/oauth/jira_dvcs/authorizations_controller.rb b/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
index 613999f4ca7..03921761f45 100644
--- a/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
+++ b/app/controllers/oauth/jira_dvcs/authorizations_controller.rb
@@ -8,6 +8,8 @@ class Oauth::JiraDvcs::AuthorizationsController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
+ before_action :validate_redirect_uri, only: :new
+
feature_category :integrations
# 1. Rewire Jira OAuth initial request to our stablished OAuth authorization URL.
@@ -56,4 +58,15 @@ class Oauth::JiraDvcs::AuthorizationsController < ApplicationController
def normalize_scope(scope)
scope == 'repo' ? 'api' : scope
end
+
+ def validate_redirect_uri
+ client = Doorkeeper::OAuth::Client.find(params[:client_id])
+ return render_404 unless client
+
+ return true if Doorkeeper::OAuth::Helpers::URIChecker.valid_for_authorization?(
+ params['redirect_uri'], client.redirect_uri
+ )
+
+ render_403
+ end
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 7890502cf0e..c542ffbce7e 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -117,7 +117,7 @@ class NotesFinder
when "snippet", "project_snippet"
SnippetsFinder.new(@current_user, project: @project).execute # rubocop: disable CodeReuse/Finder
when "personal_snippet"
- PersonalSnippet.all
+ SnippetsFinder.new(@current_user, only_personal: true).execute # rubocop: disable CodeReuse/Finder
else
raise "invalid target_type '#{noteable_type}'"
end
diff --git a/app/graphql/mutations/release_asset_links/create.rb b/app/graphql/mutations/release_asset_links/create.rb
index f6445514ce9..bda998764b9 100644
--- a/app/graphql/mutations/release_asset_links/create.rb
+++ b/app/graphql/mutations/release_asset_links/create.rb
@@ -36,13 +36,15 @@ module Mutations
raise_resource_not_available_error!
end
- new_link = release.links.create(link_attrs)
-
- unless new_link.persisted?
- return { link: nil, errors: new_link.errors.full_messages }
+ result = ::Releases::Links::CreateService
+ .new(release, current_user, link_attrs)
+ .execute
+
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
end
-
- { link: new_link, errors: [] }
end
end
end
diff --git a/app/graphql/mutations/release_asset_links/delete.rb b/app/graphql/mutations/release_asset_links/delete.rb
index 91fa74859f6..9a75b472411 100644
--- a/app/graphql/mutations/release_asset_links/delete.rb
+++ b/app/graphql/mutations/release_asset_links/delete.rb
@@ -21,11 +21,15 @@ module Mutations
def resolve(id:)
link = authorized_find!(id)
- unless link.destroy
- return { link: nil, errors: link.errors.full_messages }
+ result = ::Releases::Links::DestroyService
+ .new(link.release, current_user)
+ .execute(link)
+
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
end
-
- { link: link, errors: [] }
end
def find_object(id)
diff --git a/app/graphql/mutations/release_asset_links/update.rb b/app/graphql/mutations/release_asset_links/update.rb
index f9368927371..2e9054c290d 100644
--- a/app/graphql/mutations/release_asset_links/update.rb
+++ b/app/graphql/mutations/release_asset_links/update.rb
@@ -46,11 +46,15 @@ module Mutations
def resolve(id:, **link_attrs)
link = authorized_find!(id)
- unless link.update(link_attrs)
- return { link: nil, errors: link.errors.full_messages }
- end
+ result = ::Releases::Links::UpdateService
+ .new(link.release, current_user, link_attrs)
+ .execute(link)
- { link: link, errors: [] }
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
+ end
end
def find_object(id)
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 34a378abaa4..b66296d2627 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -172,6 +172,13 @@ module UsersHelper
'https://about.gitlab.com/free-trial/'
end
+ def user_profile_tabs_app_data(user)
+ {
+ user_calendar_path: user_calendar_path(user, :json),
+ utc_offset: local_timezone_instance(user.timezone).now.utc_offset
+ }
+ end
+
private
def admin_users_paths
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 9749703bb48..3dab7056249 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -77,6 +77,7 @@ module Ci
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
delegate :ensure_persistent_ref, to: :pipeline
delegate :enable_debug_trace!, to: :metadata
+ delegate :debug_trace_enabled?, to: :metadata
serialize :options # rubocop:disable Cop/ActiveRecordSerialize
serialize :yaml_variables, Gitlab::Serializer::Ci::Variables # rubocop:disable Cop/ActiveRecordSerialize
@@ -1078,11 +1079,10 @@ module Ci
end
def debug_mode?
- # TODO: Have `debug_mode?` check against data on sent back from runner
- # to capture all the ways that variables can be set.
- # See (https://gitlab.com/gitlab-org/gitlab/-/issues/290955)
- variables['CI_DEBUG_TRACE']&.value&.casecmp('true') == 0 ||
- variables['CI_DEBUG_SERVICES']&.value&.casecmp('true') == 0
+ # perform the check on both sides in case the runner version is old
+ debug_trace_enabled? ||
+ Gitlab::Utils.to_boolean(variables['CI_DEBUG_SERVICES']&.value, default: false) ||
+ Gitlab::Utils.to_boolean(variables['CI_DEBUG_TRACE']&.value, default: false)
end
def drop_with_exit_code!(failure_reason, exit_code)
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 333a176b8f3..c43da8b215e 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -43,14 +43,6 @@ class CommitStatus < Ci::ApplicationRecord
scope :order_id_desc, -> { order(id: :desc) }
- scope :exclude_ignored, -> do
- # We want to ignore failed but allowed to fail jobs.
- #
- # TODO, we also skip ignored optional manual actions.
- where("allow_failure = ? OR status IN (?)",
- false, all_state_names - [:failed, :canceled, :manual])
- end
-
scope :latest, -> { where(retried: [false, nil]) }
scope :retried, -> { where(retried: true) }
scope :ordered, -> { order(:name) }
@@ -239,10 +231,6 @@ class CommitStatus < Ci::ApplicationRecord
name.to_s.sub(regex, '').strip
end
- def failed_but_allowed?
- allow_failure? && (failed? || canceled?)
- end
-
# Time spent running.
def duration
calculate_duration(started_at, finished_at)
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 6aa3ef52670..15fae97895e 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -510,7 +510,7 @@ class Integration < ApplicationRecord
end
def api_field_names
- fields.reject { _1[:type] == 'password' }.pluck(:name)
+ fields.reject { _1[:type] == 'password' || _1[:name] == 'webhook' }.pluck(:name)
end
def form_fields
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index 80eecc14d0f..3b3c7d8f2cd 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -15,6 +15,7 @@ module Integrations
TAG_KEY_VALUE_RE = %r{\A [\w-]+ : .*\S.* \z}x.freeze
field :datadog_site,
+ exposes_secrets: true,
placeholder: DEFAULT_DOMAIN,
help: -> do
ERB::Util.html_escape(
diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb
index 142f466018b..2f0995e9ab0 100644
--- a/app/models/integrations/prometheus.rb
+++ b/app/models/integrations/prometheus.rb
@@ -3,6 +3,7 @@
module Integrations
class Prometheus < BaseMonitoring
include PrometheusAdapter
+ include Gitlab::Utils::StrongMemoize
field :manual_configuration,
type: 'checkbox',
@@ -81,7 +82,7 @@ module Integrations
allow_local_requests: allow_local_api_url?
)
- if behind_iap?
+ if behind_iap? && iap_client
# Adds the Authorization header
options[:headers] = iap_client.apply({})
end
@@ -106,6 +107,22 @@ module Integrations
should_return_client?
end
+ alias_method :google_iap_service_account_json_raw, :google_iap_service_account_json
+ private :google_iap_service_account_json_raw
+
+ MASKED_VALUE = '*' * 8
+
+ def google_iap_service_account_json
+ json = google_iap_service_account_json_raw
+ return json unless json.present?
+
+ Gitlab::Json.parse(json)
+ .then { |hash| hash.transform_values { MASKED_VALUE } }
+ .then { |hash| Gitlab::Json.generate(hash) }
+ rescue Gitlab::Json.parser_error
+ json
+ end
+
private
delegate :allow_local_requests_from_web_hooks_and_services?, to: :current_settings, private: true
@@ -155,17 +172,21 @@ module Integrations
end
def clean_google_iap_service_account
- return unless google_iap_service_account_json
+ json = google_iap_service_account_json_raw
+ return unless json.present?
- google_iap_service_account_json
- .then { |json| Gitlab::Json.parse(json) }
- .except('token_credential_uri')
+ Gitlab::Json.parse(json).except('token_credential_uri')
+ rescue Gitlab::Json.parser_error
+ {}
end
def iap_client
@iap_client ||= Google::Auth::Credentials
.new(clean_google_iap_service_account, target_audience: google_iap_audience_client_id)
.client
+ rescue StandardError
+ nil
end
+ strong_memoize_attr :iap_client
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 0a9705181ba..7e9fd9dad54 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -51,6 +51,7 @@ module Groups
publish_event(old_root_ancestor_id)
end
+ # Overridden in EE
def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root?
raise_transfer_error(:same_parent_as_current) if same_parent?
@@ -208,6 +209,7 @@ module Groups
raise TransferError, localized_error_messages[message]
end
+ # Overridden in EE
def localized_error_messages
{
database_not_supported: s_('TransferGroup|Database is not supported.'),
diff --git a/app/services/releases/links/base_service.rb b/app/services/releases/links/base_service.rb
new file mode 100644
index 00000000000..939de982db4
--- /dev/null
+++ b/app/services/releases/links/base_service.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Releases
+ module Links
+ class BaseService
+ attr_accessor :release, :current_user, :params
+
+ def initialize(release, current_user = nil, params = {})
+ @release = release
+ @current_user = current_user
+ @params = params.dup
+ end
+
+ private
+
+ def allowed_params
+ @allowed_params ||= params.slice(:name, :url, :link_type).tap do |hash|
+ hash[:filepath] = filepath if provided_filepath?
+ end
+ end
+
+ def provided_filepath?
+ params.key?(:direct_asset_path) || params.key?(:filepath)
+ end
+
+ def filepath
+ params[:direct_asset_path] || params[:filepath]
+ end
+ end
+ end
+end
diff --git a/app/services/releases/links/create_service.rb b/app/services/releases/links/create_service.rb
new file mode 100644
index 00000000000..c73c9f40254
--- /dev/null
+++ b/app/services/releases/links/create_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Releases
+ module Links
+ class CreateService < BaseService
+ def execute
+ return ServiceResponse.error(message: _('Access Denied')) unless allowed?
+
+ link = release.links.create(allowed_params)
+
+ if link.persisted?
+ ServiceResponse.success(payload: { link: link })
+ else
+ ServiceResponse.error(message: link.errors.full_messages)
+ end
+ end
+
+ private
+
+ def allowed?
+ Ability.allowed?(current_user, :create_release, release)
+ end
+ end
+ end
+end
diff --git a/app/services/releases/links/destroy_service.rb b/app/services/releases/links/destroy_service.rb
new file mode 100644
index 00000000000..9edde2f357b
--- /dev/null
+++ b/app/services/releases/links/destroy_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Releases
+ module Links
+ class DestroyService < BaseService
+ def execute(link)
+ return ServiceResponse.error(message: _('Access Denied')) unless allowed?
+ return ServiceResponse.error(message: _('Link does not exist')) unless link
+
+ if link.destroy
+ ServiceResponse.success(payload: { link: link })
+ else
+ ServiceResponse.error(message: link.errors.full_messages)
+ end
+ end
+
+ private
+
+ def allowed?
+ Ability.allowed?(current_user, :destroy_release, release)
+ end
+ end
+ end
+end
diff --git a/app/services/releases/links/update_service.rb b/app/services/releases/links/update_service.rb
new file mode 100644
index 00000000000..f50cde5c5a9
--- /dev/null
+++ b/app/services/releases/links/update_service.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Releases
+ module Links
+ class UpdateService < BaseService
+ def execute(link)
+ return ServiceResponse.error(message: _('Access Denied')) unless allowed?
+ return ServiceResponse.error(message: _('Link does not exist')) unless link
+
+ if link.update(allowed_params)
+ ServiceResponse.success(payload: { link: link })
+ else
+ ServiceResponse.error(message: link.errors.full_messages)
+ end
+ end
+
+ private
+
+ def allowed?
+ Ability.allowed?(current_user, :update_release, release)
+ end
+ end
+ end
+end
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index 86fb2cdf114..396d6a19dea 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -121,7 +121,7 @@ module ResourceAccessTokens
def do_not_allow_owner_access_level_for_project_bot?(access_level)
resource.is_a?(Project) &&
- access_level == Gitlab::Access::OWNER &&
+ access_level.to_i == Gitlab::Access::OWNER &&
!current_user.can?(:manage_owners, resource)
end
end
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 968189ab310..e16a2235e53 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,5 +1,4 @@
- page_title _('CI/CD Analytics')
-- add_page_specific_style 'page_bundles/project_quality'
#js-project-pipelines-charts-app{ data: { project_path: @project.full_path,
should_render_dora_charts: should_render_dora_charts.to_s,
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index b9290972656..04236bb988d 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -128,52 +128,50 @@
.profile-user-bio
= @user.bio
- - unless profile_tabs.empty?
- - if Feature.enabled?(:profile_tabs_vue, current_user)
- #js-profile-tabs
- - else
- .scrolling-tabs-container
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
- %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
- - if profile_tab?(:overview)
- %li.js-overview-tab
- = link_to user_path, data: { target: 'div#js-overview', action: 'overview', toggle: 'tab' } do
- = s_('UserProfile|Overview')
- - if profile_tab?(:activity)
- %li.js-activity-tab
- = link_to user_activity_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
- = s_('UserProfile|Activity')
- - unless Feature.enabled?(:security_auto_fix) && @user.bot?
- - if profile_tab?(:groups)
- %li.js-groups-tab
- = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do
- = s_('UserProfile|Groups')
- - if profile_tab?(:contributed)
- %li.js-contributed-tab
- = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do
- = s_('UserProfile|Contributed projects')
- - if profile_tab?(:projects)
- %li.js-projects-tab
- = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
- = s_('UserProfile|Personal projects')
- - if profile_tab?(:starred)
- %li.js-starred-tab
- = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do
- = s_('UserProfile|Starred projects')
- - if profile_tab?(:snippets)
- %li.js-snippets-tab
- = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
- = s_('UserProfile|Snippets')
- - if profile_tab?(:followers)
- %li.js-followers-tab
- = link_to user_followers_path, data: { target: 'div#followers', action: 'followers', toggle: 'tab', endpoint: user_followers_path(format: :json) } do
- = s_('UserProfile|Followers')
- - if profile_tab?(:following)
- %li.js-following-tab
- = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json) } do
- = s_('UserProfile|Following')
-
+ - if !profile_tabs.empty? && !Feature.enabled?(:profile_tabs_vue, current_user)
+ .scrolling-tabs-container
+ .fade-left= sprite_icon('chevron-lg-left', size: 12)
+ .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs
+ - if profile_tab?(:overview)
+ %li.js-overview-tab
+ = link_to user_path, data: { target: 'div#js-overview', action: 'overview', toggle: 'tab' } do
+ = s_('UserProfile|Overview')
+ - if profile_tab?(:activity)
+ %li.js-activity-tab
+ = link_to user_activity_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do
+ = s_('UserProfile|Activity')
+ - unless Feature.enabled?(:security_auto_fix) && @user.bot?
+ - if profile_tab?(:groups)
+ %li.js-groups-tab
+ = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do
+ = s_('UserProfile|Groups')
+ - if profile_tab?(:contributed)
+ %li.js-contributed-tab
+ = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do
+ = s_('UserProfile|Contributed projects')
+ - if profile_tab?(:projects)
+ %li.js-projects-tab
+ = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
+ = s_('UserProfile|Personal projects')
+ - if profile_tab?(:starred)
+ %li.js-starred-tab
+ = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do
+ = s_('UserProfile|Starred projects')
+ - if profile_tab?(:snippets)
+ %li.js-snippets-tab
+ = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
+ = s_('UserProfile|Snippets')
+ - if profile_tab?(:followers)
+ %li.js-followers-tab
+ = link_to user_followers_path, data: { target: 'div#followers', action: 'followers', toggle: 'tab', endpoint: user_followers_path(format: :json) } do
+ = s_('UserProfile|Followers')
+ - if profile_tab?(:following)
+ %li.js-following-tab
+ = link_to user_following_path, data: { target: 'div#following', action: 'following', toggle: 'tab', endpoint: user_following_path(format: :json) } do
+ = s_('UserProfile|Following')
+ - if !profile_tabs.empty? && Feature.enabled?(:profile_tabs_vue, current_user)
+ #js-profile-tabs{ data: user_profile_tabs_app_data(@user) }
%div{ class: container_class }
- unless Feature.enabled?(:profile_tabs_vue, current_user)
.tab-content
diff --git a/config/application.rb b/config/application.rb
index 94195be4481..71e3076057d 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -312,7 +312,6 @@ module Gitlab
config.assets.precompile << "page_bundles/pipeline_editor.css"
config.assets.precompile << "page_bundles/productivity_analytics.css"
config.assets.precompile << "page_bundles/profile.css"
- config.assets.precompile << "page_bundles/project_quality.css"
config.assets.precompile << "page_bundles/profile_two_factor_auth.css"
config.assets.precompile << "page_bundles/profiles/preferences.css"
config.assets.precompile << "page_bundles/project.css"
diff --git a/config/feature_flags/development/code_basic_search_files_by_regexp.yml b/config/feature_flags/development/code_basic_search_files_by_regexp.yml
deleted file mode 100644
index e68581bdfbd..00000000000
--- a/config/feature_flags/development/code_basic_search_files_by_regexp.yml
+++ /dev/null
@@ -1,9 +0,0 @@
----
-name: code_basic_search_files_by_regexp
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/109987
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389551
-milestone: '15.9'
-type: development
-group: group::global search
-default_enabled: false
-
diff --git a/config/initializers/0_1_yaml_safe_load_file_patch.rb b/config/initializers/0_1_yaml_safe_load_file_patch.rb
new file mode 100644
index 00000000000..f43712900eb
--- /dev/null
+++ b/config/initializers/0_1_yaml_safe_load_file_patch.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# TODO: Remove this once we're on Ruby 3
+# https://gitlab.com/gitlab-org/gitlab/-/issues/393651
+unless YAML.respond_to?(:safe_load_file)
+ module YAML
+ # Temporary Ruby 2 back-compat workaround.
+ #
+ # This method only exists as of stdlib 3.0.0:
+ # https://ruby-doc.org/stdlib-3.0.0/libdoc/psych/rdoc/Psych.html
+ def self.safe_load_file(path, **options)
+ YAML.safe_load(File.read(path), **options)
+ end
+ end
+end
diff --git a/config/initializers/rest-client-hostname_override.rb b/config/initializers/rest-client-hostname_override.rb
index 2fb3b9fc27d..b647fe9cac8 100644
--- a/config/initializers/rest-client-hostname_override.rb
+++ b/config/initializers/rest-client-hostname_override.rb
@@ -14,7 +14,7 @@ module RestClient
self.hostname_override = hostname_override
rescue Gitlab::UrlBlocker::BlockedUrlError => e
- raise ArgumentError, "URL '#{uri}' is blocked: #{e.message}"
+ raise ArgumentError, "URL is blocked: #{e.message}"
end
# Gitlab::UrlBlocker returns a Addressable::URI which we need to coerce
diff --git a/danger/qa_selector/Dangerfile b/danger/qa_selector/Dangerfile
index b781f390a1a..f049915dc42 100644
--- a/danger/qa_selector/Dangerfile
+++ b/danger/qa_selector/Dangerfile
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+return if helper.stable_branch?
+
data_qa_selectors = /qa_selector|data-qa-selector/
deprecated_qa_selectors = /(?!.*\bdata-qa-)(?=class=.*qa-.*|class: .*qa-.*)/
diff --git a/doc/administration/geo/replication/datatypes.md b/doc/administration/geo/replication/datatypes.md
index 27e1b990b2b..d159a83a178 100644
--- a/doc/administration/geo/replication/datatypes.md
+++ b/doc/administration/geo/replication/datatypes.md
@@ -139,7 +139,7 @@ We use PostgreSQL's own replication functionality to replicate data from the **p
We use Redis both as a cache store and to hold persistent data for our background jobs system. Because both
use-cases have data that are exclusive to the same Geo site, we don't replicate it between sites.
-Elasticsearch is an optional database that for advanced searching capabilities. It can improve search
+Elasticsearch is an optional database for advanced search. It can improve search
in both source-code level, and user generated content in issues, merge requests, and discussions.
Elasticsearch is not supported in Geo.
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 9f4477bcbf7..68e16b56624 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -66,7 +66,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages
to GitLab users through the UI.
- [Elasticsearch](../integration/advanced_search/elasticsearch.md): Enable Elasticsearch to
- empower Advanced Search. Use when you deal with a huge amount of data.
+ empower advanced search. Use when you deal with a huge amount of data.
- [External Classification Policy Authorization](../user/admin_area/settings/external_authorization.md)
- [Add a license](../user/admin_area/license.md): Add a license to unlock
features that are in paid tiers of GitLab.
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index b1d97fd16a9..2ba2930c6f7 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -922,7 +922,7 @@ Reports that go over the 20 MB limit aren't loaded. Affected reports:
- [CI/CD parameter `artifacts:expose_as`](../ci/yaml/index.md#artifactsexpose_as)
- [Unit test reports](../ci/testing/unit_test_reports.md)
-## Advanced Search limits
+## Advanced search limits
### Maximum file size indexed
@@ -945,7 +945,7 @@ is pre-allocated during indexing.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201826) in GitLab 12.8.
-You can set a limit on the content of text fields indexed for Advanced Search.
+You can set a limit on the content of text fields indexed for advanced search.
Setting a maximum helps to reduce the load of the indexing processes. If any
text field exceeds this limit, then the text is truncated to this number of
characters. The rest of the text is not indexed, and not searchable.
diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md
index 916b50a410d..7b7111017a7 100644
--- a/doc/administration/logs/index.md
+++ b/doc/administration/logs/index.md
@@ -180,7 +180,7 @@ In addition, the log contains the originating IP address,
(`remote_ip`), the user's ID (`user_id`), and username (`username`).
Some endpoints (such as `/search`) may make requests to Elasticsearch if using
-[Advanced Search](../../user/search/advanced_search.md). These
+[advanced search](../../user/search/advanced_search.md). These
additionally log `elasticsearch_calls` and `elasticsearch_call_duration_s`,
which correspond to:
diff --git a/doc/administration/reference_architectures/10k_users.md b/doc/administration/reference_architectures/10k_users.md
index 2512616ee04..7ee3a0ce327 100644
--- a/doc/administration/reference_architectures/10k_users.md
+++ b/doc/administration/reference_architectures/10k_users.md
@@ -170,7 +170,7 @@ To set up GitLab and its components to accommodate up to 10,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage)
used for shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
The servers start on the same 10.6.0.0/24 private network range, and can
@@ -2223,9 +2223,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search
+## Configure advanced search
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/1k_users.md b/doc/administration/reference_architectures/1k_users.md
index d3aa6fef51e..adcf8eeb4c6 100644
--- a/doc/administration/reference_architectures/1k_users.md
+++ b/doc/administration/reference_architectures/1k_users.md
@@ -78,9 +78,9 @@ You can also optionally configure GitLab to use an [external PostgreSQL service]
or an [external object storage service](../object_storage.md) for added
performance and reliability at an increased complexity cost.
-## Configure Advanced Search **(PREMIUM SELF)**
+## Configure advanced search **(PREMIUM SELF)**
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/25k_users.md b/doc/administration/reference_architectures/25k_users.md
index 5234916d7e8..2319cfb0356 100644
--- a/doc/administration/reference_architectures/25k_users.md
+++ b/doc/administration/reference_architectures/25k_users.md
@@ -170,7 +170,7 @@ To set up GitLab and its components to accommodate up to 25,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage)
used for shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
The servers start on the same 10.6.0.0/24 private network range, and can
@@ -2241,9 +2241,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search
+## Configure advanced search
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/2k_users.md b/doc/administration/reference_architectures/2k_users.md
index ee26504902c..62589ac3375 100644
--- a/doc/administration/reference_architectures/2k_users.md
+++ b/doc/administration/reference_architectures/2k_users.md
@@ -101,7 +101,7 @@ To set up GitLab and its components to accommodate up to 2,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage) used for
shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
## Configure the external load balancer
@@ -911,9 +911,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search **(PREMIUM SELF)**
+## Configure advanced search **(PREMIUM SELF)**
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/3k_users.md b/doc/administration/reference_architectures/3k_users.md
index 8f35e71dcfb..b9fcd60327e 100644
--- a/doc/administration/reference_architectures/3k_users.md
+++ b/doc/administration/reference_architectures/3k_users.md
@@ -176,7 +176,7 @@ To set up GitLab and its components to accommodate up to 3,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage)
used for shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
The servers start on the same 10.6.0.0/24 private network range, and can
@@ -2188,9 +2188,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search
+## Configure advanced search
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md
index 6346eb118b8..c6bad8d97b0 100644
--- a/doc/administration/reference_architectures/50k_users.md
+++ b/doc/administration/reference_architectures/50k_users.md
@@ -170,7 +170,7 @@ To set up GitLab and its components to accommodate up to 50,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage)
used for shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
The servers start on the same 10.6.0.0/24 private network range, and can
@@ -2240,9 +2240,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search
+## Configure advanced search
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/administration/reference_architectures/5k_users.md b/doc/administration/reference_architectures/5k_users.md
index c0eff587f68..d41e8ff6f04 100644
--- a/doc/administration/reference_architectures/5k_users.md
+++ b/doc/administration/reference_architectures/5k_users.md
@@ -173,7 +173,7 @@ To set up GitLab and its components to accommodate up to 5,000 users:
environment.
1. [Configure the object storage](#configure-the-object-storage)
used for shared data objects.
-1. [Configure Advanced Search](#configure-advanced-search) (optional) for faster,
+1. [Configure advanced search](#configure-advanced-search) (optional) for faster,
more advanced code search across your entire GitLab instance.
The servers start on the same 10.6.0.0/24 private network range, and can
@@ -2187,9 +2187,9 @@ GitLab Runner returns job logs in chunks which Omnibus GitLab caches temporarily
While sharing the job logs through NFS is supported, it's recommended to avoid the need to use NFS by enabling [incremental logging](../job_logs.md#incremental-logging-architecture) (required when no NFS node has been deployed). Incremental logging uses Redis instead of disk space for temporary caching of job logs.
-## Configure Advanced Search
+## Configure advanced search
-You can leverage Elasticsearch and [enable Advanced Search](../../integration/advanced_search/elasticsearch.md)
+You can leverage Elasticsearch and [enable advanced search](../../integration/advanced_search/elasticsearch.md)
for faster, more advanced code search across your entire GitLab instance.
Elasticsearch cluster design and requirements are dependent on your specific
diff --git a/doc/api/search.md b/doc/api/search.md
index 8882897d2f2..f7fa7babe71 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -31,8 +31,8 @@ GET /search
| `scope` | string | Yes | The scope to search in. Values include `projects`, `issues`, `merge_requests`, `milestones`, `snippet_titles`, `users`. [Additional scopes](#additional-scopes): `blobs`, `commits`, `notes`, `wiki_blobs`. |
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports `issues` scope; other scopes are ignored. |
-| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
-| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
+| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
+| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
| `state` | string | No | Filter by state. Supports `issues` and `merge_requests` scopes; other scopes are ignored. |
### Scope: `projects`
@@ -450,8 +450,8 @@ GET /groups/:id/search
| `scope` | string | Yes | The scope to search in. Values include `issues`, `merge_requests`, `milestones`, `projects`, `users`. [Additional scopes](#additional-scopes): `blobs`, `commits`, `notes`, `wiki_blobs`. |
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports only `issues` scope; other scopes are ignored. |
-| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
-| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
+| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
+| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
| `state` | string | No | Filter by state. Supports `issues` and `merge_requests` only; other scopes are ignored. |
The response depends on the requested scope.
@@ -841,8 +841,8 @@ GET /projects/:id/search
| `search` | string | Yes | The search query. |
| `confidential` | boolean | No | Filter by confidentiality. Supports `issues` scope; other scopes are ignored. |
| `ref` | string | No | The name of a repository branch or tag to search on. The project's default branch is used by default. Applicable only for scopes `blobs`, `commits`, and `wiki_blobs`. |
-| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
-| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for Advanced Search.|
+| `order_by` | string | No | Allowed values are `created_at` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
+| `sort` | string | No | Allowed values are `asc` or `desc` only. If not set, results are sorted by `created_at` in descending order for basic search, or by the most relevant documents for advanced search.|
| `state` | string | No | Filter by state. Supports the `issues` and `merge_requests` scopes; other scopes are ignored. |
The response depends on the requested scope.
diff --git a/doc/architecture/blueprints/search/code_search_with_zoekt.md b/doc/architecture/blueprints/search/code_search_with_zoekt.md
index d0d347f1ff4..ca74460b98a 100644
--- a/doc/architecture/blueprints/search/code_search_with_zoekt.md
+++ b/doc/architecture/blueprints/search/code_search_with_zoekt.md
@@ -57,7 +57,7 @@ customers that wish to participate in the trial.
The main goals of this integration will be to implement the following highly
requested improvements to code search:
-1. [Exact match (substring match) code searches in Advanced Search](https://gitlab.com/gitlab-org/gitlab/-/issues/325234)
+1. [Exact match (substring match) code searches in advanced search](https://gitlab.com/gitlab-org/gitlab/-/issues/325234)
1. [Support regular expressions with Advanced Global Search](https://gitlab.com/gitlab-org/gitlab/-/issues/4175)
1. [Support multiple line matches in the same file](https://gitlab.com/gitlab-org/gitlab/-/issues/668)
diff --git a/doc/development/advanced_search.md b/doc/development/advanced_search.md
index dd05c1475ec..d326f1fdb99 100644
--- a/doc/development/advanced_search.md
+++ b/doc/development/advanced_search.md
@@ -4,7 +4,7 @@ group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Advanced Search development
+# Advanced search development
This page includes information about developing and working with Elasticsearch.
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 87ed232c644..2cbf0e21bf6 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -448,7 +448,7 @@ Consul is a tool for service discovery and configuration. Consul is distributed,
- [Source](../integration/advanced_search/elasticsearch.md)
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/elasticsearch.md)
- Layer: Core Service (Data)
-- GitLab.com: [Get Advanced Search working on GitLab.com (Closed)](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
+- GitLab.com: [Get advanced search working on GitLab.com (Closed)](https://gitlab.com/groups/gitlab-org/-/epics/153) epic.
Elasticsearch is a distributed RESTful search engine built for the cloud.
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 5469d3fc2a8..56b5fa8c976 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -94,7 +94,7 @@ EE: true
uses system fonts for all text."
- Any client-facing change to our REST and GraphQL APIs **must** have a changelog entry.
See the [complete list what comprises a GraphQL breaking change](api_graphql_styleguide.md#breaking-changes).
-- Any change that introduces an [Advanced Search migration](search/advanced_search_migration_styleguide.md#creating-a-new-advanced-search-migration)
+- Any change that introduces an [advanced search migration](search/advanced_search_migration_styleguide.md#creating-a-new-advanced-search-migration)
**must** have a changelog entry.
- A fix for a regression introduced and then fixed in the same release (such as
fixing a bug introduced during a monthly release candidate) **should not**
diff --git a/doc/development/documentation/feature_flags.md b/doc/development/documentation/feature_flags.md
index 37be2178592..3d71a180087 100644
--- a/doc/development/documentation/feature_flags.md
+++ b/doc/development/documentation/feature_flags.md
@@ -57,13 +57,6 @@ Possible version history entries are:
> - [Generally available](issue-link) in GitLab X.Y. Feature flag `flag_name` removed.
```
-You can combine entries if they happened in the same release:
-
-```markdown
-> - Introduced in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
-> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/337507) in GitLab 14.3.
-```
-
## Use a note to describe the state of the feature flag
Information about feature flags should be in a `FLAG` note at the start of the topic (just below the version history).
@@ -144,3 +137,41 @@ And, when the feature is done and fully available to all users:
> - [Enabled on GitLab.com](https://gitlab.com/issue/etc) in GitLab 13.9.
> - [Generally available](issue-link) in GitLab 14.0. Feature flag `forti_token_cloud` removed.
```
+
+## Simplify long version history
+
+The version history can get long, but you can sometimes simplify or remove entries.
+
+Combine entries if they happened in the same release:
+
+- Before:
+
+ ```markdown
+ > - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
+ > - [Enabled on GitLab.com](issue-link) in GitLab 14.3.
+ > - [Enabled on self-managed](issue-link) in GitLab 14.3.
+ ```
+
+- After:
+
+ ```markdown
+ > - [Introduced](issue-link) in GitLab 14.2 [with a flag](../../administration/feature_flags.md) named `ci_include_rules`. Disabled by default.
+ > - [Enabled on GitLab.com and self-managed](issue-link) in GitLab 14.3.
+ ```
+
+Remove `Enabled on GitLab.com` entries when the feature is enabled by default for both GitLab.com and self-managed:
+
+- Before:
+
+ ```markdown
+ > - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
+ > - [Enabled on GitLab.com](issue-link) in GitLab 15.9.
+ > - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
+ ```
+
+- After:
+
+ ```markdown
+ > - [Introduced](issue-link) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_hooks_pre_get_sources_script`. Disabled by default.
+ > - [Generally available](issue-link) in GitLab 15.10. Feature flag `ci_hooks_pre_get_sources_script` removed.
+ ```
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index d13f255d2c6..b6fc0743ce7 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -82,6 +82,11 @@ If you can add the phrase "by zombies" to the phrase,
the construction is passive. For example, `The button is selected by zombies`
is passive. `Zombies select the button` is active.
+## Admin Area
+
+Use title case for **Admin Area** to refer to the area of the UI that you access when you select **Main menu > Admin**.
+This area of the UI says **Admin Area** at the top of the page and on the menu.
+
## administrator
Use **administrator access** instead of **admin** when talking about a user's access level.
@@ -99,10 +104,9 @@ Instead of:
- To do this thing, you must have the Admin role.
-## Admin Area
+## advanced search
-Use title case **Admin Area** to refer to the area of the UI that you access when you select **Main menu > Admin**.
-This area of the UI says **Admin Area** at the top of the page and on the menu.
+Use lowercase for **advanced search** to refer to the faster, more efficient search across the entire GitLab instance.
## agent
@@ -113,7 +117,7 @@ For example:
- Install the agent in your cluster.
- Select an agent from the list.
-Do not use title case **GitLab Agent** or **GitLab Agent for Kubernetes**.
+Do not use title case for **GitLab Agent** or **GitLab Agent for Kubernetes**.
## agent access token
diff --git a/doc/development/feature_development.md b/doc/development/feature_development.md
index 4c1d01e0752..c3cee596d8a 100644
--- a/doc/development/feature_development.md
+++ b/doc/development/feature_development.md
@@ -79,7 +79,7 @@ Consult these topics for information on contributing to specific GitLab features
- [Adding a new Redis instance](redis/new_redis_instance.md)
- [Sidekiq guidelines](sidekiq/index.md) for working with Sidekiq workers
- [Working with Gitaly](gitaly.md)
-- [Advanced Search integration docs](advanced_search.md)
+- [Advanced search integration docs](advanced_search.md)
- [Working with merge request diffs](diffs.md)
- [Approval Rules](merge_request_concepts/approval_rules.md)
- [Repository mirroring](repository_mirroring.md)
diff --git a/doc/development/fips_compliance.md b/doc/development/fips_compliance.md
index 04500a7534a..45668b6be0c 100644
--- a/doc/development/fips_compliance.md
+++ b/doc/development/fips_compliance.md
@@ -67,7 +67,7 @@ listed here that also do not work properly in FIPS mode:
- [Static Application Security Testing (SAST)](../user/application_security/sast/index.md)
supports a reduced set of [analyzers](../user/application_security/sast/index.md#fips-enabled-images)
when operating in FIPS-compliant mode.
-- Advanced Search is currently not included in FIPS mode. It must not be enabled to be FIPS-compliant.
+- Advanced search is currently not included in FIPS mode. It must not be enabled to be FIPS-compliant.
- [Gravatar or Libravatar-based profile images](../administration/libravatar.md) are not FIPS-compliant.
Additionally, these package repositories are disabled in FIPS mode:
diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md
index f8814f96f1b..b9c05429bd3 100644
--- a/doc/development/integrations/index.md
+++ b/doc/development/integrations/index.md
@@ -249,6 +249,15 @@ To expose the integration in the [REST API](../../api/integrations.md):
You can also refer to our [REST API style guide](../api_styleguide.md).
+Sensitive fields are not exposed over the API. Sensitive fields are those fields that contain any of the following in their name:
+
+- `key`
+- `passphrase`
+- `password`
+- `secret`
+- `token`
+- `webhook`
+
#### GraphQL API
Integrations use the `Types::Projects::ServiceType` type by default,
diff --git a/doc/development/search/advanced_search_migration_styleguide.md b/doc/development/search/advanced_search_migration_styleguide.md
index 474f224ff4a..2c14c056b96 100644
--- a/doc/development/search/advanced_search_migration_styleguide.md
+++ b/doc/development/search/advanced_search_migration_styleguide.md
@@ -4,9 +4,9 @@ group: Global Search
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Advanced Search migration style guide
+# Advanced search migration style guide
-## Creating a new Advanced Search migration
+## Creating a new advanced search migration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/234046) in GitLab 13.6.
@@ -217,13 +217,13 @@ end
### Multi-version compatibility
-These Advanced Search migrations, like any other GitLab changes, need to support the case where
+These advanced search migrations, like any other GitLab changes, need to support the case where
[multiple versions of the application are running at the same time](../multi_version_compatibility.md).
Depending on the order of deployment, it's possible that the migration
has started or finished and there's still a server running the application code from before the
migration. We need to take this into consideration until we can
-[ensure all Advanced Search migrations start after the deployment has finished](https://gitlab.com/gitlab-org/gitlab/-/issues/321619).
+[ensure all advanced search migrations start after the deployment has finished](https://gitlab.com/gitlab-org/gitlab/-/issues/321619).
### Reverting a migration
@@ -236,7 +236,7 @@ some data is moved) to a later merge request after the migrations have
completed successfully. To be safe, for self-managed customers we should also
defer it to another release if there is risk of important data loss.
-### Best practices for Advanced Search migrations
+### Best practices for advanced search migrations
Follow these best practices for best results:
@@ -253,9 +253,9 @@ Follow these best practices for best results:
- Consider adding a retry limit if there is potential for the migration to fail.
This ensures that migrations can be halted if an issue occurs.
-## Deleting Advanced Search migrations in a major version upgrade
+## Deleting advanced search migrations in a major version upgrade
-Because our Advanced Search migrations usually require us to support multiple
+Because our advanced search migrations usually require us to support multiple
code paths for a long period of time, it's important to clean those up when we
safely can.
@@ -265,7 +265,7 @@ backwards compatibility for indices that have not been fully migrated. We
We also choose to replace the migration code with the halted migration
and remove tests so that:
-- We don't need to maintain any code that is called from our Advanced Search
+- We don't need to maintain any code that is called from our advanced search
migrations.
- We don't waste CI time running tests for migrations that we don't support
anymore.
@@ -281,7 +281,7 @@ GitLab.com to `%14.0` before the migrations in `%13.12` were finished. Because
our deployments to GitLab.com are automated and we don't have
automated checks to prevent this, the extra precaution is warranted.
Additionally, even if we did have automated checks to prevent it, we wouldn't
-actually want to hold up GitLab.com deployments on Advanced Search migrations,
+actually want to hold up GitLab.com deployments on advanced search migrations,
as they may still have another week to go, and that's too long to block
deployments.
diff --git a/doc/index.md b/doc/index.md
index 492320d93aa..6e9b93216e0 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -43,7 +43,7 @@ Have a look at some of our most popular topics:
| [Activate GitLab EE with a license](user/admin_area/license.md) | Activate GitLab Enterprise Edition functionality with a license. |
| [Back up and restore GitLab](raketasks/backup_restore.md) | Rake tasks for backing up and restoring GitLab self-managed instances. |
| [GitLab release and maintenance policy](policy/maintenance.md) | Policies for version naming and cadence, and also upgrade recommendations. |
-| [Elasticsearch integration](integration/advanced_search/elasticsearch.md) | Integrate Elasticsearch with GitLab to enable advanced searching. |
+| [Elasticsearch integration](integration/advanced_search/elasticsearch.md) | Integrate Elasticsearch with GitLab to enable advanced search. |
| [Omnibus GitLab database settings](https://docs.gitlab.com/omnibus/settings/database.html) | Database settings for Omnibus GitLab self-managed instances. |
| [Omnibus GitLab NGINX settings](https://docs.gitlab.com/omnibus/settings/nginx.html) | NGINX settings for Omnibus GitLab self-managed instances. |
| [Omnibus GitLab SSL configuration](https://docs.gitlab.com/omnibus/settings/ssl.html) | SSL settings for Omnibus GitLab self-managed instances. |
diff --git a/doc/install/next_steps.md b/doc/install/next_steps.md
index c718d895c8a..70b6101b1eb 100644
--- a/doc/install/next_steps.md
+++ b/doc/install/next_steps.md
@@ -56,7 +56,7 @@ installation.
## Cross-repository Code Search
-- [Advanced Search](../integration/advanced_search/elasticsearch.md): Leverage [Elasticsearch](https://www.elastic.co/) or [OpenSearch](https://opensearch.org/) for
+- [Advanced search](../integration/advanced_search/elasticsearch.md): Leverage [Elasticsearch](https://www.elastic.co/) or [OpenSearch](https://opensearch.org/) for
faster, more advanced code search across your entire GitLab instance.
## Scaling and replication
diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md
index 31684de026a..7221b11caaf 100644
--- a/doc/integration/advanced_search/elasticsearch.md
+++ b/doc/integration/advanced_search/elasticsearch.md
@@ -7,8 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Elasticsearch **(PREMIUM SELF)**
-This page describes how to enable Advanced Search. When enabled,
-Advanced Search provides faster search response times and [improved search features](../../user/search/advanced_search.md).
+This page describes how to enable advanced search. When enabled,
+advanced search provides faster search response times and [improved search features](../../user/search/advanced_search.md).
## Version requirements
@@ -16,7 +16,7 @@ Advanced Search provides faster search response times and [improved search featu
> Support for Elasticsearch 6.8 was [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/350275) in GitLab 15.0.
-Advanced Search works with the following versions of Elasticsearch.
+Advanced search works with the following versions of Elasticsearch.
| GitLab version | Elasticsearch version |
|-----------------------|--------------------------|
@@ -25,7 +25,7 @@ Advanced Search works with the following versions of Elasticsearch.
| GitLab 13.3 - 13.8 | Elasticsearch 6.4 - 7.x |
| GitLab 12.7 - 13.2 | Elasticsearch 6.x - 7.x |
-Advanced Search follows the [Elasticsearch end-of-life policy](https://www.elastic.co/support/eol).
+Advanced search follows the [Elasticsearch end-of-life policy](https://www.elastic.co/support/eol).
When we change Elasticsearch supported versions in GitLab, we announce them in [deprecation notes](https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations) in monthly release posts
before we remove them.
@@ -46,7 +46,7 @@ If you are using a compatible version and after connecting to OpenSearch, you ge
Elasticsearch requires additional resources to those documented in the
[GitLab system requirements](../../install/requirements.md).
-Memory, CPU, and storage resource amounts vary depending on the amount of data you index into the Elasticsearch cluster. Heavily used Elasticsearch clusters may require more resources. The [`estimate_cluster_size`](#gitlab-advanced-search-rake-tasks) Rake task ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221177) in GitLab 13.10) uses the total repository size to estimate the Advanced Search storage requirements.
+Memory, CPU, and storage resource amounts vary depending on the amount of data you index into the Elasticsearch cluster. Heavily used Elasticsearch clusters may require more resources. The [`estimate_cluster_size`](#gitlab-advanced-search-rake-tasks) Rake task ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221177) in GitLab 13.10) uses the total repository size to estimate the advanced search storage requirements.
## Install Elasticsearch
@@ -163,20 +163,20 @@ Errors from the [GitLab Elasticsearch Indexer](https://gitlab.com/gitlab-org/git
the [`elasticsearch.log`](../../administration/logs/index.md#elasticsearchlog) file and the [`sidekiq.log`](../../administration/logs/index.md#sidekiqlog) file with a `json.exception.class` of `Gitlab::Elastic::Indexer::Error`.
These errors may occur when indexing Git repository data.
-## Enable Advanced Search
+## Enable advanced search
For GitLab instances with more than 50 GB repository data you can follow the instructions for [how to index large instances efficiently](#how-to-index-large-instances-efficiently) below.
-To enable Advanced Search, you must have administrator access to GitLab:
+To enable advanced search, you must have administrator access to GitLab:
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Settings > Advanced Search**.
NOTE:
- To see the Advanced Search section, you need an active GitLab Premium
+ To see the **Advanced Search** section, you need an active GitLab Premium
[license](../../user/admin_area/license.md).
-1. Configure the [Advanced Search settings](#advanced-search-configuration) for
+1. Configure the [advanced search settings](#advanced-search-configuration) for
your Elasticsearch cluster. Do not enable **Search with Elasticsearch enabled**
yet.
1. Enable **Elasticsearch indexing** and select **Save changes**. This creates
@@ -202,7 +202,7 @@ you might have problems updating documents such as issues because your
instance queues a job to index the change, but cannot find a valid
Elasticsearch cluster.
-### Advanced Search configuration
+### Advanced search configuration
The following Elasticsearch settings are available:
@@ -294,16 +294,16 @@ For more information, see [Identity and Access Management in Amazon OpenSearch S
When using fine-grained access control with a user in the internal database, you should use HTTP basic
authentication to connect to OpenSearch. You can provide the master username and password as part of the
-OpenSearch URL or in the **Username** and **Password** text boxes in the Advanced Search settings. See
+OpenSearch URL or in the **Username** and **Password** text boxes in the advanced search settings. See
[Tutorial: Internal user database and HTTP basic authentication](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac-walkthrough-basic.html) for details.
##### Connecting with an IAM user
-When using fine-grained access control with IAM credentials, you can provide the credentials in the **AWS OpenSearch IAM credentials** section in the Advanced Search settings.
+When using fine-grained access control with IAM credentials, you can provide the credentials in the **AWS OpenSearch IAM credentials** section in the advanced search settings.
##### Permissions for fine-grained access control
-The following permissions are required for Advanced Search. See [Creating roles](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html#fgac-roles) for details.
+The following permissions are required for advanced search. See [Creating roles](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/fgac.html#fgac-roles) for details.
```json
{
@@ -338,7 +338,7 @@ The following permissions are required for Advanced Search. See [Creating roles]
}
```
-The index pattern `*` requires a few permissions for Advanced Search to work.
+The index pattern `*` requires a few permissions for advanced search to work.
### Limit the number of namespaces and projects that can be indexed
@@ -350,14 +350,14 @@ under **Elasticsearch indexing restrictions** more options become available.
You can select namespaces and projects to index exclusively. If the namespace is a group, it includes
any subgroups and projects belonging to those subgroups to be indexed as well.
-Advanced Search only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search does not provide a code or commit scope. This is possible only in the scope of an indexed namespace. There is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
+Advanced search only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search does not provide a code or commit scope. This is possible only in the scope of an indexed namespace. There is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
You can filter the selection dropdown list by writing part of the namespace or project name you're interested in.
![limit namespace filter](img/limit_namespace_filter.png)
NOTE:
-If no namespaces or projects are selected, no Advanced Search indexing takes place.
+If no namespaces or projects are selected, no advanced search indexing takes place.
WARNING:
If you have already indexed your instance, you must regenerate the index to delete all existing data
@@ -385,11 +385,11 @@ For guidance on what to install, see the following Elasticsearch language plugin
| Parameter | Description |
|-------------------------------------------------------|-------------|
| `Enable Chinese (smartcn) custom analyzer: Indexing` | Enables or disables Chinese language support using [`smartcn`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) custom analyzer for newly created indices.|
-| `Enable Chinese (smartcn) custom analyzer: Search` | Enables or disables using [`smartcn`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) fields for Advanced Search. Only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html), enabling custom analyzer indexing and recreating the index.|
+| `Enable Chinese (smartcn) custom analyzer: Search` | Enables or disables using [`smartcn`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html) fields for advanced search. Only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-smartcn.html), enabling custom analyzer indexing and recreating the index.|
| `Enable Japanese (kuromoji) custom analyzer: Indexing` | Enables or disables Japanese language support using [`kuromoji`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) custom analyzer for newly created indices.|
-| `Enable Japanese (kuromoji) custom analyzer: Search` | Enables or disables using [`kuromoji`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) fields for Advanced Search. Only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html), enabling custom analyzer indexing and recreating the index.|
+| `Enable Japanese (kuromoji) custom analyzer: Search` | Enables or disables using [`kuromoji`](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html) fields for advanced search. Only enable this after [installing the plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html), enabling custom analyzer indexing and recreating the index.|
-## Disable Advanced Search
+## Disable advanced search
To disable the Elasticsearch integration:
@@ -423,7 +423,7 @@ the writes to the `primary` index. Then, we create another index and invoke the
index data onto the new index. After the reindexing job is complete, we switch to the new index by connecting the
index alias to it which becomes the new `primary` index. At the end, we resume the writes and typical operation resumes.
-### Trigger the reindex via the Advanced Search administration
+### Trigger the reindex via the advanced search administration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34069) in GitLab 13.2.
> - A scheduled index deletion and the ability to cancel it was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38914) in GitLab 13.3.
@@ -502,15 +502,15 @@ Sometimes, you might want to abandon the unfinished reindex job and resume the i
1. Expand **Advanced Search**.
1. Clear the **Pause Elasticsearch indexing** checkbox.
-## Advanced Search migrations
+## Advanced search migrations
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/234046) in GitLab 13.6.
With reindex migrations running in the background, there's no need for a manual
intervention. This usually happens in situations where new features are added to
-Advanced Search, which means adding or changing the way content is indexed.
+advanced search, which means adding or changing the way content is indexed.
-To confirm that the Advanced Search migrations ran, you can check with:
+To confirm that the advanced search migrations ran, you can check with:
```shell
curl "$CLUSTER_URL/gitlab-production-migrations/_search?q=*" | jq .
@@ -554,7 +554,7 @@ To debug issues with the migrations you can check the [`elasticsearch.log` file]
### Retry a halted migration
Some migrations are built with a retry limit. If the migration cannot finish within the retry limit,
-it is halted and a notification is displayed in the Advanced Search integration settings.
+it is halted and a notification is displayed in the advanced search integration settings.
It is recommended to check the [`elasticsearch.log` file](../../administration/logs/index.md#elasticsearchlog) to
debug why the migration was halted and make any changes before retrying the migration. Once you believe you've
fixed the cause of the failure, select "Retry migration", and the migration is scheduled to be retried
@@ -575,7 +575,7 @@ version. If you have halted migrations, these need to be resolved and
[retried](#retry-a-halted-migration) before proceeding with a major version
upgrade. Read more about [upgrading to a new major version](../../update/index.md#upgrading-to-a-new-major-version).
-## GitLab Advanced Search Rake tasks
+## GitLab advanced search Rake tasks
Rake tasks are available to:
@@ -587,7 +587,7 @@ The following are some available Rake tasks:
| Task | Description |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [`sudo gitlab-rake gitlab:elastic:info`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Outputs debugging information for the Advanced Search integration. |
+| [`sudo gitlab-rake gitlab:elastic:info`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Outputs debugging information for the advanced search integration. |
| [`sudo gitlab-rake gitlab:elastic:index`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enables Elasticsearch indexing and run `gitlab:elastic:create_empty_index`, `gitlab:elastic:clear_index_status`, `gitlab:elastic:index_projects`, `gitlab:elastic:index_snippets`, and `gitlab:elastic:index_users`. |
| [`sudo gitlab-rake gitlab:elastic:pause_indexing`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Pauses Elasticsearch indexing. Changes are still tracked. Useful for cluster/index migrations. |
| [`sudo gitlab-rake gitlab:elastic:resume_indexing`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Resumes Elasticsearch indexing. |
@@ -604,7 +604,7 @@ The following are some available Rake tasks:
| [`sudo gitlab-rake gitlab:elastic:mark_reindex_failed`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Mark the most recent re-index job as failed. |
| [`sudo gitlab-rake gitlab:elastic:list_pending_migrations`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | List pending migrations. Pending migrations include those that have not yet started, have started but not finished, and those that are halted. |
| [`sudo gitlab-rake gitlab:elastic:estimate_cluster_size`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Get an estimate of cluster size based on the total repository size. |
-| [`sudo gitlab-rake gitlab:elastic:enable_search_with_elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enable advanced search with Elasticsearch. |
+| [`sudo gitlab-rake gitlab:elastic:enable_search_with_elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Enables advanced search with Elasticsearch. |
| [`sudo gitlab-rake gitlab:elastic:disable_search_with_elasticsearch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/tasks/gitlab/elastic.rake) | Disables advanced search with Elasticsearch. |
### Environment variables
@@ -633,7 +633,7 @@ Indexing project repositories...I, [2019-03-04T21:27:03.083410 #3384] INFO -- :
I, [2019-03-04T21:27:05.215266 #3384] INFO -- : Indexing GitLab User / test (ID=33) is done!
```
-## Advanced Search index scopes
+## Advanced search index scopes
When performing a search, the GitLab index uses the following scopes:
@@ -671,7 +671,7 @@ For basic guidance on choosing a cluster configuration you may refer to [Elastic
- `refresh_interval` is a per index setting. You may want to adjust that from default `1s` to a bigger value if you don't need data in real-time. This changes how soon you see fresh results. If that's important for you, you should leave it as close as possible to the default value.
- You might want to raise [`indices.memory.index_buffer_size`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indexing-buffer.html) to 30% or 40% if you have a lot of heavy indexing operations.
-### Advanced Search integration settings guidance
+### Advanced search integration settings guidance
- The `Number of Elasticsearch shards` setting usually corresponds with the number of CPUs available in your cluster. For example, if you have a 3-node cluster with 4 cores each, this means you benefit from having at least 3*4=12 shards in the cluster. It's only possible to change the shards number by using [Split index API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html) or by reindexing to a different index with a changed number of shards.
- The `Number of Elasticsearch replicas` setting should most of the time be equal to `1` (each shard has 1 replica). Using `0` is not recommended, because losing one node corrupts the index.
@@ -976,7 +976,7 @@ for the changes to take effect.
Sometimes there may be issues with your Elasticsearch index data and as such
GitLab allows you to revert to "basic search" when there are no search
results and assuming that basic search is supported in that scope. This "basic
-search" behaves as though you don't have Advanced Search enabled at all for
+search" behaves as though you don't have advanced search enabled at all for
your instance and search using other data sources (such as PostgreSQL data and Git
data).
diff --git a/doc/integration/advanced_search/elasticsearch_troubleshooting.md b/doc/integration/advanced_search/elasticsearch_troubleshooting.md
index f2f732386a7..0c4895f34fa 100644
--- a/doc/integration/advanced_search/elasticsearch_troubleshooting.md
+++ b/doc/integration/advanced_search/elasticsearch_troubleshooting.md
@@ -101,12 +101,12 @@ Here are some common pitfalls and how to overcome them.
There are a couple of ways to achieve that:
- When you perform a search, in the upper-right corner of the search results page,
- **Advanced search functionality is enabled** is displayed.
+ **Advanced search is enabled** is displayed.
This is always correctly identifying whether the current project/namespace
being searched is using Elasticsearch.
- From the Admin Area under **Settings > Advanced Search** check that the
- Advanced Search settings are checked.
+ advanced search settings are checked.
Those same settings there can be obtained from the Rails console if necessary:
@@ -212,7 +212,7 @@ More [complex Elasticsearch API calls](https://www.elastic.co/guide/en/elasticse
If the results:
-- Sync up, check that you are using [supported syntax](../../user/search/advanced_search.md#syntax). Advanced Search does not support [exact substring matching](https://gitlab.com/gitlab-org/gitlab/-/issues/325234).
+- Sync up, check that you are using [supported syntax](../../user/search/advanced_search.md#syntax). Advanced search does not support [exact substring matching](https://gitlab.com/gitlab-org/gitlab/-/issues/325234).
- Do not match up, this indicates a problem with the documents generated from the project. It is best to [re-index that project](../advanced_search/elasticsearch.md#indexing-a-range-of-projects-or-a-specific-project).
NOTE:
@@ -247,7 +247,7 @@ sudo gitlab-rake gitlab:elastic:clear_locked_projects
If `ElasticCommitIndexerWorker` Sidekiq workers are failing with this error during indexing, it usually means that Elasticsearch is unable to keep up with the concurrency of indexing request. To address change the following settings:
-- To decrease the indexing throughput you can decrease `Bulk request concurrency` (see [Advanced Search settings](elasticsearch.md#advanced-search-configuration)). This is set to `10` by default, but you change it to as low as 1 to reduce the number of concurrent indexing operations.
+- To decrease the indexing throughput you can decrease `Bulk request concurrency` (see [Advanced search settings](elasticsearch.md#advanced-search-configuration)). This is set to `10` by default, but you change it to as low as 1 to reduce the number of concurrent indexing operations.
- If changing `Bulk request concurrency` didn't help, you can use the [routing rules](../../administration/sidekiq/processing_specific_job_classes.md#routing-rules) option to [limit indexing jobs only to specific Sidekiq nodes](elasticsearch.md#index-large-instances-with-dedicated-sidekiq-nodes-or-processes), which should reduce the number of indexing requests.
### Indexing is very slow or fails with `rejected execution of coordinating operation` messages
@@ -437,11 +437,11 @@ Improvements to the `code_analyzer` pattern and filters are being discussed in [
In GitLab 13.9, a change was made where [binary file names are being indexed](https://gitlab.com/gitlab-org/gitlab/-/issues/301083). However, without indexing all projects' data from scratch, only binary files that are added or updated after the GitLab 13.9 release are searchable.
-## How does Advanced Search handle private projects?
+## How does advanced search handle private projects?
-Advanced Search stores all the projects in the same Elasticsearch indices,
+Advanced search stores all the projects in the same Elasticsearch indices,
however, searches only surface results that can be viewed by the user.
-Advanced Search honors all permission checks in the application by
+Advanced search honors all permission checks in the application by
filtering out projects that a user does not have access to at search time.
### Role mapping when using fine-grained access control with AWS Elasticsearch or OpenSearch
diff --git a/doc/integration/index.md b/doc/integration/index.md
index 195890ea4d8..02860339b6f 100644
--- a/doc/integration/index.md
+++ b/doc/integration/index.md
@@ -73,7 +73,7 @@ You can integrate GitLab with the following feature enhancements:
or [Kroki](../administration/integration/kroki.md) to use diagrams in AsciiDoc and Markdown documents.
- Attach merge requests to [Trello](trello_power_up.md) cards.
- Enable integrated code intelligence powered by [Sourcegraph](sourcegraph.md).
-- Add [Elasticsearch](advanced_search/elasticsearch.md) for [Advanced Search](../user/search/advanced_search.md).
+- Add [Elasticsearch](advanced_search/elasticsearch.md) for [advanced search](../user/search/advanced_search.md).
## Troubleshooting
diff --git a/doc/subscriptions/bronze_starter.md b/doc/subscriptions/bronze_starter.md
index 91da875a049..accc0627a41 100644
--- a/doc/subscriptions/bronze_starter.md
+++ b/doc/subscriptions/bronze_starter.md
@@ -106,7 +106,7 @@ the tiers are no longer mentioned in GitLab documentation:
- Search:
- [Filtering merge requests](../user/project/merge_requests/index.md#filter-the-list-of-merge-requests) by approvers
- [Filtering merge requests](../user/project/merge_requests/index.md#filter-the-list-of-merge-requests) by "approved by"
- - [Advanced Search (Elasticsearch)](../user/search/advanced_search.md)
+ - [Advanced search (Elasticsearch)](../user/search/advanced_search.md)
- [Service Desk](../user/project/service_desk.md)
- [Storage usage statistics](../user/usage_quotas.md#storage-usage-statistics)
@@ -128,7 +128,7 @@ Bronze-level subscribers:
- [Group iterations API](../api/group_iterations.md)
- Project milestones API: [Get all burndown chart events for a single milestone](../api/milestones.md#get-all-burndown-chart-events-for-a-single-milestone)
- [Project iterations API](../api/iterations.md)
- - Fields in the [Search API](../api/search.md) available only to [Advanced Search (Elasticsearch)](../integration/advanced_search/elasticsearch.md) users
+ - Fields in the [Search API](../api/search.md) available only to [advanced search (Elasticsearch)](../integration/advanced_search/elasticsearch.md) users
- Fields in the [Merge requests API](../api/merge_requests.md) for [merge request approvals](../user/project/merge_requests/approvals/index.md)
- Fields in the [Protected branches API](../api/protected_branches.md) that specify users or groups allowed to merge
- [Merge request approvals API](../api/merge_request_approvals.md)
diff --git a/doc/subscriptions/gitlab_dedicated/index.md b/doc/subscriptions/gitlab_dedicated/index.md
index 7ca00d11e1e..b196aae549a 100644
--- a/doc/subscriptions/gitlab_dedicated/index.md
+++ b/doc/subscriptions/gitlab_dedicated/index.md
@@ -52,6 +52,10 @@ Private connectivity via [AWS PrivateLink](https://aws.amazon.com/privatelink/)
Data is encrypted at rest and in transit using the latest encryption standards.
+#### Bring your own key encryption
+
+During onboarding, you can specify an AWS KMS encryption key stored in your own AWS account that GitLab uses to encrypt the data for your Dedicated instance. This gives you full control over the data you store in GitLab.
+
### Compliance
#### Certifications
@@ -97,6 +101,17 @@ GitLab Dedicated comes with the self-managed [Ultimate feature set](https://abou
With GitLab Dedicated, you must [install the GitLab Runner application](https://docs.gitlab.com/runner/install/index.html) on infrastructure that you own or manage. If hosting GitLab Runners on AWS, you can avoid having requests from the Runner fleet route through the public internet by setting up a secure connection from the Runner VPC to the GitLab Dedicated endpoint via AWS Private Link. Learn more about [networking options](#secure-networking).
+#### Migration
+
+To help you migrate your data to GitLab Dedicated, you can choose from the following options:
+
+1. When migrating from another GitLab instance, you can either:
+ - Use the UI, including [group import](../../user/group/import/index.md) and [project import](../../user/project/settings/import_export.md).
+ - Use APIs, including the [group import API](../../api/group_import_export.md) and [project import API](../../api/project_import_export.md).
+ - Note: Import functionality behind a feature flag (such as `bulk_import_project`) is not supported in GitLab Dedicated.
+1. When migrating from third-party services, you can use [the GitLab importers](../../user/project/import/index.md#available-project-importers).
+1. You can perform a fully-automated migration through the [Congregate Automation Tool](../../user/project/import/index.md#automate-group-and-project-import), which supports migrating from existing GitLab instances as well as third-party services.
+
## Features that are not available
### GitLab application features
@@ -105,7 +120,7 @@ The following GitLab application features are not available:
- LDAP, Smartcard, or Kerberos authentication
- Multiple login providers
-- Advanced Search
+- Advanced search
- GitLab Pages
- FortiAuthenticator, or FortiToken 2FA
- Reply-by email
@@ -123,7 +138,6 @@ The following features will not be supported:
The following operational features are not available:
- Custom domains
-- Bring Your Own Key (BYOK) encryption
- Multiple Geo secondaries (Geo replicas) beyond the secondary site included by default
- Self-serve purchasing and configuration
- Multiple login providers
diff --git a/doc/update/index.md b/doc/update/index.md
index b12839ab227..48ec98e5370 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -107,11 +107,11 @@ To address the above two scenarios, it is advised to do the following prior to u
as your GitLab version. Both versions [should be the same](https://docs.gitlab.com/runner/#gitlab-runner-versions).
1. Unpause your runners and unblock new jobs from starting by reverting the previous `/etc/gitlab/gitlab.rb` change.
-## Checking for pending Advanced Search migrations **(PREMIUM SELF)**
+## Checking for pending advanced search migrations **(PREMIUM SELF)**
This section is only applicable if you have enabled the [Elasticsearch integration](../integration/advanced_search/elasticsearch.md) **(PREMIUM SELF)**.
-Major releases require all [Advanced Search migrations](../integration/advanced_search/elasticsearch.md#advanced-search-migrations)
+Major releases require all [advanced search migrations](../integration/advanced_search/elasticsearch.md#advanced-search-migrations)
to be finished from the most recent minor release in your current version
before the major version upgrade. You can find pending migrations by
running the following command:
@@ -129,16 +129,16 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:elastic:list_pending_migrations
```
-### What do you do if your Advanced Search migrations are stuck?
+### What do you do if your advanced search migrations are stuck?
-In GitLab 15.0, an Advanced Search migration named `DeleteOrphanedCommit` can be permanently stuck
+In GitLab 15.0, an advanced search migration named `DeleteOrphanedCommit` can be permanently stuck
in a pending state across upgrades. This issue
[is corrected in GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89539).
-If you are a self-managed customer who uses GitLab 15.0 with Advanced Search, you will experience performance degradation.
+If you are a self-managed customer who uses GitLab 15.0 with advanced search, you will experience performance degradation.
To clean up the migration, upgrade to 15.1 or later.
-For other Advanced Search migrations stuck in pending, see [how to retry a halted migration](../integration/advanced_search/elasticsearch.md#retry-a-halted-migration).
+For other advanced search migrations stuck in pending, see [how to retry a halted migration](../integration/advanced_search/elasticsearch.md#retry-a-halted-migration).
### What do you do for the error `Elasticsearch version not compatible`
@@ -166,7 +166,7 @@ It's also important to ensure that any [background migrations have been fully co
before upgrading to a new major version.
If you have enabled the [Elasticsearch integration](../integration/advanced_search/elasticsearch.md) **(PREMIUM SELF)**, then
-[ensure all Advanced Search migrations are completed](#checking-for-pending-advanced-search-migrations) in the last minor version in
+[ensure all advanced search migrations are completed](#checking-for-pending-advanced-search-migrations) in the last minor version in
your current version
before proceeding with the major version upgrade.
diff --git a/doc/update/package/convert_to_ee.md b/doc/update/package/convert_to_ee.md
index 7ebb860746b..0edb08b9268 100644
--- a/doc/update/package/convert_to_ee.md
+++ b/doc/update/package/convert_to_ee.md
@@ -116,7 +116,7 @@ The steps can be summed up to:
sudo rm /etc/yum.repos.d/gitlab_gitlab-ce.repo
```
-1. Optional. [Set up the Elasticsearch integration](../../integration/advanced_search/elasticsearch.md) to enable [Advanced Search](../../user/search/advanced_search.md).
+1. Optional. [Set up the Elasticsearch integration](../../integration/advanced_search/elasticsearch.md) to enable [advanced search](../../user/search/advanced_search.md).
That's it! You can now use GitLab Enterprise Edition! To update to a newer
version, follow [Update using the official repositories](index.md#upgrade-using-the-official-repositories).
diff --git a/doc/update/plan_your_upgrade.md b/doc/update/plan_your_upgrade.md
index 5b4ecb96747..0d0084cf530 100644
--- a/doc/update/plan_your_upgrade.md
+++ b/doc/update/plan_your_upgrade.md
@@ -172,7 +172,7 @@ If you have Kubernetes clusters connected with GitLab, [upgrade your GitLab agen
#### Elasticsearch
-Before updating GitLab, confirm Advanced Search migrations are complete by
+Before updating GitLab, confirm advanced search migrations are complete by
[checking for pending advanced search migrations](background_migrations.md).
After updating GitLab, you may have to upgrade
diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md
index bb9a9c371a2..297210ab8ef 100644
--- a/doc/user/clusters/agent/install/index.md
+++ b/doc/user/clusters/agent/install/index.md
@@ -155,10 +155,6 @@ helm upgrade --install gitlab-agent gitlab/gitlab-agent \
...
```
-NOTE:
-DNS rebind protection is disabled when either the HTTP_PROXY or the HTTPS_PROXY environment variable is set,
-and the domain DNS can't be resolved.
-
#### Advanced installation method
GitLab also provides a [KPT package for the agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/tree/master/build/deployment/gitlab-agent). This method provides greater flexibility, but is only recommended for advanced users.
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index 9298dab6f64..eeebb5a166c 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -7,8 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Import your project from GitHub to GitLab **(FREE)**
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381902) in GitLab 15.8, GitLab no longer automatically creates namespaces or groups that don't exist. GitLab also no longer falls back to using the user's personal namespace if the namespace or group name is taken.
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378267) in GitLab 15.9, GitLab instances behind proxies no longer require `github.com` and `api.github.com` entries in the [allowlist for local requests](../../../security/webhooks.md#create-an-allowlist-for-local-requests).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381902) in GitLab 15.8, GitLab no longer automatically creates namespaces or groups that don't exist. GitLab also no longer falls back to using the user's personal namespace if the namespace or group name is taken.
You can import your GitHub projects from either GitHub.com or GitHub Enterprise. Importing projects does not
migrate or import any types of groups or organizations from GitHub to GitLab.
@@ -63,8 +62,9 @@ prerequisites for those imports.
If you are importing from GitHub Enterprise to a self-managed GitLab instance:
- You must first enable the [GitHub integration](../../../integration/github.md).
-- For GitLab 15.8 and earlier, you must add `github.com` and `api.github.com` entries in the
- [allowlist for local requests](../../../security/webhooks.md#create-an-allowlist-for-local-requests).
+- If GitLab is behind a HTTP/HTTPS proxy, you must populate the [allowlist for local requests](../../../security/webhooks.md#create-an-allowlist-for-local-requests)
+ with `github.com` and `api.github.com` to solve the hostname. For more information, read the issue
+ [Importing a GitHub project requires DNS resolution even when behind a proxy](https://gitlab.com/gitlab-org/gitlab/-/issues/37941).
### Importing from GitHub.com to self-managed GitLab
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
index e55cf337d16..a92f65b064e 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
@@ -5,10 +5,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# DNS records overview **(FREE)**
-
-_Read this document for a brief overview of DNS records in the scope
-of GitLab Pages, for beginners in web development._
+# GitLab Pages DNS records **(FREE)**
A Domain Name System (DNS) web service routes visitors to websites
by translating domain names (such as `www.example.com`) into the
@@ -74,7 +71,7 @@ Example:
This way, visitors visiting `www.example.com` are redirected to
`example.com`.
-## MX record
+## `MX` record
MX records are used to define the mail exchanges that are used for the domain.
This helps email messages arrive at your mail server correctly.
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 24e9e6e15a4..3bb62921979 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
@@ -5,7 +5,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Custom domains and SSL/TLS certificates **(FREE)**
+# GitLab Pages custom domains **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238461) in GitLab 15.4, you can use verified domains to [bypass user email confirmation for SAML- or SCIM-provisioned users](../../../group/saml_sso/index.md#bypass-user-email-confirmation-with-verified-domains).
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index 95ac2e50f29..34a2f221327 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -6,7 +6,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# GitLab Pages integration with Let's Encrypt **(FREE)**
+# GitLab Pages Let's Encrypt certificates **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28996) in GitLab 12.1.
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
index 398d8dc6e1e..0ba00177659 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
@@ -5,10 +5,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# SSL/TLS certificates **(FREE)**
-
-_Read this document for a brief overview of SSL/TLS certificates in
-the scope of GitLab Pages, for beginners in web development._
+# GitLab Pages SSL/TLS certificates **(FREE)**
Every GitLab Pages project on GitLab.com is available under
HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
diff --git a/doc/user/project/pages/getting_started/pages_ci_cd_template.md b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
index f596e418b02..37f74d18d4c 100644
--- a/doc/user/project/pages/getting_started/pages_ci_cd_template.md
+++ b/doc/user/project/pages/getting_started/pages_ci_cd_template.md
@@ -5,7 +5,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Create a Pages website by using a CI/CD template **(FREE)**
+# Create a GitLab Pages website from a CI/CD template **(FREE)**
GitLab provides `.gitlab-ci.yml` templates for the most popular Static Site Generators (SSGs).
You can create your own `.gitlab-ci.yml` file from one of these templates, and run
diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
index ec195f0f0e4..a864c114b3e 100644
--- a/doc/user/project/pages/getting_started/pages_forked_sample_project.md
+++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md
@@ -5,7 +5,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Create a Pages website from a forked sample **(FREE)**
+# Create a GitLab Pages website from a forked sample project **(FREE)**
GitLab provides [sample projects for the most popular Static Site Generators (SSG)](https://gitlab.com/pages).
You can fork one of the sample projects and run the CI/CD pipeline to generate a Pages website.
diff --git a/doc/user/project/pages/getting_started/pages_new_project_template.md b/doc/user/project/pages/getting_started/pages_new_project_template.md
index a301d2b64be..7b6efaa7af4 100644
--- a/doc/user/project/pages/getting_started/pages_new_project_template.md
+++ b/doc/user/project/pages/getting_started/pages_new_project_template.md
@@ -4,7 +4,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Create a Pages website from a template **(FREE)**
+# Create a GitLab Pages website from a project template **(FREE)**
GitLab provides templates for the most popular Static Site Generators (SSGs).
You can create a new project from a template and run the CI/CD pipeline to generate a Pages website.
diff --git a/doc/user/project/pages/getting_started/pages_ui.md b/doc/user/project/pages/getting_started/pages_ui.md
index 91c2c532a9a..00635fe6db2 100644
--- a/doc/user/project/pages/getting_started/pages_ui.md
+++ b/doc/user/project/pages/getting_started/pages_ui.md
@@ -4,7 +4,7 @@ group: Incubation
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Create a Pages deployment for your static site **(FREE)**
+# Create a GitLab Pages deployment for a static site **(FREE)**
If you already have a GitLab project that contains your static site or framework,
you can generate a GitLab Pages website from it.
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index a0c8073d6eb..b286c59916a 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -4,7 +4,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# GitLab Pages domain names, URLs, and base URLs **(FREE)**
+# GitLab Pages default domain names and URLs **(FREE)**
On this document, learn how to name your project for GitLab Pages
according to your intended website's URL.
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 51c42eeecb8..0a8348f468e 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -4,7 +4,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Exploring GitLab Pages **(FREE)**
+# GitLab Pages settings **(FREE)**
This document is a user guide to explore the options and settings
GitLab Pages offers.
diff --git a/doc/user/project/pages/public_folder.md b/doc/user/project/pages/public_folder.md
index 751db136e34..536ecd8fa81 100644
--- a/doc/user/project/pages/public_folder.md
+++ b/doc/user/project/pages/public_folder.md
@@ -6,7 +6,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Configure the public files folder **(FREE)**
+# GitLab Pages public folder **(FREE)**
All the files that should be accessible by the browser must be in a root-level folder called `public`.
diff --git a/doc/user/project/pages/redirects.md b/doc/user/project/pages/redirects.md
index f5447fd67ca..75cb1474dc2 100644
--- a/doc/user/project/pages/redirects.md
+++ b/doc/user/project/pages/redirects.md
@@ -4,7 +4,7 @@ group: Editor
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Create redirects for GitLab Pages **(FREE)**
+# GitLab Pages redirects **(FREE)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-pages/-/issues/24) in GitLab Pages 1.25.0 and GitLab 13.4 behind a feature flag, disabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab-pages/-/merge_requests/367) in GitLab 13.5.
diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md
index d1744e3bb4e..ac4c3c62746 100644
--- a/doc/user/search/advanced_search.md
+++ b/doc/user/search/advanced_search.md
@@ -5,18 +5,18 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference
---
-# Advanced Search **(PREMIUM)**
+# Advanced search **(PREMIUM)**
> Moved to GitLab Premium in 13.9.
-You can use Advanced Search for faster, more efficient search across the entire GitLab
-instance. Advanced Search is based on Elasticsearch, a purpose-built full-text search
+You can use advanced search for faster, more efficient search across the entire GitLab
+instance. Advanced search is based on Elasticsearch, a purpose-built full-text search
engine you can horizontally scale to get results in up to a second in most cases.
You can find code you want to update in all projects at once to save
maintenance time and promote innersourcing.
-You can use Advanced Search in:
+You can use advanced search in:
- Projects
- Issues
@@ -29,15 +29,15 @@ You can use Advanced Search in:
- Commits
- Project wikis (not [group wikis](../project/wiki/group.md))
-## Enable Advanced Search
+## Enable advanced search
-- On GitLab.com, Advanced Search is enabled for groups with paid subscriptions.
+- On GitLab.com, advanced search is enabled for groups with paid subscriptions.
- For self-managed GitLab instances, an administrator must
- [enable Advanced Search](../../integration/advanced_search/elasticsearch.md#enable-advanced-search).
+ [enable advanced search](../../integration/advanced_search/elasticsearch.md#enable-advanced-search).
## Syntax
-Advanced Search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html#simple-query-string-syntax). The syntax supports both exact and fuzzy search queries.
+Advanced search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html#simple-query-string-syntax). The syntax supports both exact and fuzzy search queries.
<!-- markdownlint-disable -->
@@ -90,7 +90,7 @@ In user search, a [fuzzy query](https://www.elastic.co/guide/en/elasticsearch/re
- You can only search files smaller than 1 MB.
For self-managed GitLab instances, an administrator can
[change this limit](../../integration/advanced_search/elasticsearch.md#advanced-search-configuration).
-- You can only use Advanced Search on the default branch of a project.
+- You can only use advanced search on the default branch of a project.
- The search query must not contain any of the following characters:
```plaintext
diff --git a/doc/user/search/exact_code_search.md b/doc/user/search/exact_code_search.md
index 14dca6d4a12..63878ba7539 100644
--- a/doc/user/search/exact_code_search.md
+++ b/doc/user/search/exact_code_search.md
@@ -20,7 +20,7 @@ When performing any Code search in GitLab it will choose to use "Exact Code
Search" powered by [Zoekt](https://github.com/sourcegraph/zoekt) if the project
is part of an enabled Group.
-The main differences between Zoekt and [Advanced Search](advanced_search.md)
+The main differences between Zoekt and [advanced search](advanced_search.md)
are that Zoekt provides exact substring matching as well as allows you to
search for regular expressions. Since it allows searching for regular
expressions, certain special characters will require escaping. Backslash can
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index ad2bbf90917..fa99a1a2bc8 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -86,11 +86,15 @@ module API
get ':id/repository/commits', urgency: :low do
not_found! 'Repository' unless user_project.repository_exists?
+ page = params[:page] > 0 ? params[:page] : 1
+ per_page = params[:per_page] > 0 ? params[:per_page] : Kaminari.config.default_per_page
+ limit = [per_page, Kaminari.config.max_per_page].min
+ offset = (page - 1) * limit
+
path = params[:path]
before = params[:until]
after = params[:since]
ref = params[:ref_name].presence || user_project.default_branch unless params[:all]
- offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
first_parent = params[:first_parent]
@@ -98,7 +102,7 @@ module API
commits = user_project.repository.commits(ref,
path: path,
- limit: params[:per_page],
+ limit: limit,
offset: offset,
before: before,
after: after,
diff --git a/lib/api/entities/tag.rb b/lib/api/entities/tag.rb
index 713bae64d5c..5047258dd97 100644
--- a/lib/api/entities/tag.rb
+++ b/lib/api/entities/tag.rb
@@ -3,6 +3,8 @@
module API
module Entities
class Tag < Grape::Entity
+ include RequestAwareEntity
+
expose :name, documentation: { type: 'string', example: 'v1.0.0' }
expose :message, documentation: { type: 'string', example: 'Release v1.0.0' }
expose :target, documentation: { type: 'string', example: '2695effb5807a22ff3d138d593fd856244e155e7' }
@@ -12,7 +14,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
- expose :release, using: Entities::TagRelease do |repo_tag, options|
+ expose :release, using: Entities::TagRelease, if: ->(*) { can_read_release? } do |repo_tag, options|
options[:project].releases.find_by(tag: repo_tag.name)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -20,6 +22,10 @@ module API
expose :protected, documentation: { type: 'boolean', example: true } do |repo_tag, options|
::ProtectedTag.protected?(options[:project], repo_tag.name)
end
+
+ def can_read_release?
+ can?(options[:current_user], :read_release, options[:project])
+ end
end
end
end
diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb
index b21bcb4a903..66115baf120 100644
--- a/lib/api/release/links.rb
+++ b/lib/api/release/links.rb
@@ -67,12 +67,14 @@ module API
post 'links' do
authorize! :create_release, release
- new_link = release.links.create(declared_params(include_missing: false))
+ result = ::Releases::Links::CreateService
+ .new(release, current_user, declared_params(include_missing: false))
+ .execute
- if new_link.persisted?
- present new_link, with: Entities::Releases::Link
+ if result.success?
+ present result.payload[:link], with: Entities::Releases::Link
else
- render_api_error!(new_link.errors.messages, 400)
+ render_api_error!(result.message, 400)
end
end
@@ -121,10 +123,14 @@ module API
put do
authorize! :update_release, release
- if link.update(declared_params(include_missing: false))
- present link, with: Entities::Releases::Link
+ result = ::Releases::Links::UpdateService
+ .new(release, current_user, declared_params(include_missing: false))
+ .execute(link)
+
+ if result.success?
+ present result.payload[:link], with: Entities::Releases::Link
else
- render_api_error!(link.errors.messages, 400)
+ render_api_error!(result.message, 400)
end
end
@@ -141,10 +147,14 @@ module API
delete do
authorize! :destroy_release, release
- if link.destroy
- present link, with: Entities::Releases::Link
+ result = ::Releases::Links::DestroyService
+ .new(release, current_user)
+ .execute(link)
+
+ if result.success?
+ present result.payload[:link], with: Entities::Releases::Link
else
- render_api_error!(link.errors.messages, 400)
+ render_api_error!(result.message, 400)
end
end
end
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 4ddf22c726f..f918fb997bf 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -45,7 +45,13 @@ module API
paginated_tags = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tags_finder)
- present_cached paginated_tags, with: Entities::Tag, project: user_project, cache_context: -> (_tag) { user_project.cache_key }
+ present_cached paginated_tags,
+ with: Entities::Tag,
+ project: user_project,
+ current_user: current_user,
+ cache_context: -> (_tag) do
+ [user_project.cache_key, can?(current_user, :read_release, user_project)].join(':')
+ end
rescue Gitlab::Git::InvalidPageToken => e
unprocessable_entity!(e.message)
@@ -68,7 +74,7 @@ module API
tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag
- present tag, with: Entities::Tag, project: user_project
+ present tag, with: Entities::Tag, project: user_project, current_user: current_user
end
desc 'Create a new repository tag' do
diff --git a/lib/banzai/filter/kroki_filter.rb b/lib/banzai/filter/kroki_filter.rb
index 26f42c6b194..2b9e2a22c11 100644
--- a/lib/banzai/filter/kroki_filter.rb
+++ b/lib/banzai/filter/kroki_filter.rb
@@ -9,6 +9,8 @@ module Banzai
# HTML that replaces all diagrams supported by Kroki with the corresponding img tags.
# If the source content is large then the hidden attribute is added to the img tag.
class KrokiFilter < HTML::Pipeline::Filter
+ include ActionView::Helpers::TagHelper
+
MAX_CHARACTER_LIMIT = 2000
def call
@@ -27,9 +29,11 @@ module Banzai
diagram_format = "svg"
doc.xpath(xpath).each do |node|
diagram_type = node.parent['lang'] || node['lang']
+ next unless diagram_selectors.include?(diagram_type)
+
diagram_src = node.content
image_src = create_image_src(diagram_type, diagram_format, diagram_src)
- img_tag = Nokogiri::HTML::DocumentFragment.parse(%(<img src="#{image_src}" />))
+ img_tag = Nokogiri::HTML::DocumentFragment.parse(content_tag(:img, nil, src: image_src))
img_tag = img_tag.children.first
next if img_tag.nil?
diff --git a/lib/gitlab/file_finder.rb b/lib/gitlab/file_finder.rb
index 95f896a74e9..8a894901ca1 100644
--- a/lib/gitlab/file_finder.rb
+++ b/lib/gitlab/file_finder.rb
@@ -44,15 +44,11 @@ module Gitlab
# Overridden in Gitlab::WikiFileFinder
def search_paths(query)
- if Feature.enabled?(:code_basic_search_files_by_regexp, project)
- return [] if query.blank? || ref.blank?
-
- escaped_query = RE2::Regexp.escape(query)
- query_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)#{escaped_query}")
- repository.search_files_by_regexp(query_regexp, ref)
- else
- repository.search_files_by_name(query, ref)
- end
+ return [] if query.blank? || ref.blank?
+
+ escaped_query = RE2::Regexp.escape(query)
+ query_regexp = Gitlab::EncodingHelper.encode_utf8_no_detect("(?i)#{escaped_query}")
+ repository.search_files_by_regexp(query_regexp, ref)
end
end
end
diff --git a/lib/gitlab/http_connection_adapter.rb b/lib/gitlab/http_connection_adapter.rb
index aec430f2686..c6f9f2df299 100644
--- a/lib/gitlab/http_connection_adapter.rb
+++ b/lib/gitlab/http_connection_adapter.rb
@@ -47,7 +47,7 @@ module Gitlab
dns_rebind_protection: dns_rebind_protection?,
schemes: %w[http https])
rescue Gitlab::UrlBlocker::BlockedUrlError => e
- raise Gitlab::HTTP::BlockedUrlError, "URL '#{url}' is blocked: #{e.message}"
+ raise Gitlab::HTTP::BlockedUrlError, "URL is blocked: #{e.message}"
end
def allow_local_requests?
@@ -59,6 +59,8 @@ module Gitlab
end
def dns_rebind_protection?
+ return false if Gitlab.http_proxy_env?
+
Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
end
diff --git a/lib/gitlab/octokit/middleware.rb b/lib/gitlab/octokit/middleware.rb
index 0e47672bb3c..a92860f7eb8 100644
--- a/lib/gitlab/octokit/middleware.rb
+++ b/lib/gitlab/octokit/middleware.rb
@@ -11,8 +11,7 @@ module Gitlab
Gitlab::UrlBlocker.validate!(env[:url],
schemes: %w[http https],
allow_localhost: allow_local_requests?,
- allow_local_network: allow_local_requests?,
- dns_rebind_protection: dns_rebind_protection?
+ allow_local_network: allow_local_requests?
)
@app.call(env)
@@ -23,10 +22,6 @@ module Gitlab
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
-
- def dns_rebind_protection?
- Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
- end
end
end
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index de952b37b39..13504db0413 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -139,7 +139,7 @@ module Gitlab
end
def enforce_address_info_retrievable?(uri, dns_rebind_protection)
- return false if !dns_rebind_protection || Gitlab.http_proxy_env? || domain_allowed?(uri)
+ return false if !dns_rebind_protection || domain_allowed?(uri)
# In the test suite we use a lot of mocked urls that are either invalid or
# don't exist. In order to avoid modifying a ton of tests and factories
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f89b1fbd69a..bb750079eca 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -25547,6 +25547,9 @@ msgstr ""
msgid "Link copied"
msgstr ""
+msgid "Link does not exist"
+msgstr ""
+
msgid "Link text"
msgstr ""
@@ -34995,6 +34998,9 @@ msgstr ""
msgid "ProtectedEnvironment|Allowed to deploy"
msgstr ""
+msgid "ProtectedEnvironment|Allowed to deploy and approve"
+msgstr ""
+
msgid "ProtectedEnvironment|Allowed to deploy to %{project} / %{environment}"
msgstr ""
@@ -45451,6 +45457,9 @@ msgstr ""
msgid "TransferGroup|Group is already associated to the parent group."
msgstr ""
+msgid "TransferGroup|SAML Provider or SCIM Token is configured for this group."
+msgstr ""
+
msgid "TransferGroup|The parent group already has a subgroup or a project with the same path."
msgstr ""
diff --git a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
index 124b6c9cd44..c50eb2f4fdf 100644
--- a/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/user_inherited_access_spec.rb
@@ -79,19 +79,24 @@ module QA
'is allowed to commit to sub-group project via the API',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/363349'
) do
- expect do
- Resource::Repository::Commit.fabricate_via_api! do |commit|
- commit.api_client = parent_group_user_api_client
- commit.project = sub_group_project
- commit.branch = "new_branch_#{SecureRandom.hex(8)}"
- commit.start_branch = sub_group_project.default_branch
- commit.commit_message = 'Add new file'
- commit.add_files([{ file_path: 'test.txt', content: 'new file' }])
- end
- rescue StandardError => e
- QA::Runtime::Logger.error("Full failure message: #{e.message}")
- raise
- end.not_to raise_error
+ # Retry is needed due to delays with project authorization updates
+ # Long term solution to accessing the status of a project authorization update
+ # has been proposed in https://gitlab.com/gitlab-org/gitlab/-/issues/393369
+ QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 2) do
+ expect do
+ Resource::Repository::Commit.fabricate_via_api! do |commit|
+ commit.api_client = parent_group_user_api_client
+ commit.project = sub_group_project
+ commit.branch = "new_branch_#{SecureRandom.hex(8)}"
+ commit.start_branch = sub_group_project.default_branch
+ commit.commit_message = 'Add new file'
+ commit.add_files([{ file_path: 'test.txt', content: 'new file' }])
+ end
+ rescue StandardError => e
+ QA::Runtime::Logger.error("Full failure message: #{e.message}")
+ raise
+ end.not_to raise_error
+ end
end
after do
diff --git a/spec/controllers/oauth/jira_dvcs/authorizations_controller_spec.rb b/spec/controllers/oauth/jira_dvcs/authorizations_controller_spec.rb
index 496ef7859f9..3d271a22f27 100644
--- a/spec/controllers/oauth/jira_dvcs/authorizations_controller_spec.rb
+++ b/spec/controllers/oauth/jira_dvcs/authorizations_controller_spec.rb
@@ -2,24 +2,40 @@
require 'spec_helper'
-RSpec.describe Oauth::JiraDvcs::AuthorizationsController do
+RSpec.describe Oauth::JiraDvcs::AuthorizationsController, feature_category: :integrations do
+ let_it_be(:application) { create(:oauth_application, redirect_uri: 'https://example.com/callback') }
+
describe 'GET new' do
it 'redirects to OAuth authorization with correct params' do
- get :new, params: { client_id: 'client-123', scope: 'foo', redirect_uri: 'http://example.com/' }
+ get :new, params: { client_id: application.uid, scope: 'foo', redirect_uri: 'https://example.com/callback' }
- expect(response).to redirect_to(oauth_authorization_url(client_id: 'client-123',
- response_type: 'code',
- scope: 'foo',
- redirect_uri: oauth_jira_dvcs_callback_url))
+ expect(response).to redirect_to(oauth_authorization_url(
+ client_id: application.uid,
+ response_type: 'code',
+ scope: 'foo',
+ redirect_uri: oauth_jira_dvcs_callback_url))
end
it 'replaces the GitHub "repo" scope with "api"' do
- get :new, params: { client_id: 'client-123', scope: 'repo', redirect_uri: 'http://example.com/' }
+ get :new, params: { client_id: application.uid, scope: 'repo', redirect_uri: 'https://example.com/callback' }
+
+ expect(response).to redirect_to(oauth_authorization_url(
+ client_id: application.uid,
+ response_type: 'code',
+ scope: 'api',
+ redirect_uri: oauth_jira_dvcs_callback_url))
+ end
+
+ it 'returns 404 with an invalid client' do
+ get :new, params: { client_id: 'client-123', scope: 'foo', redirect_uri: 'https://example.com/callback' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 403 with an incorrect redirect_uri' do
+ get :new, params: { client_id: application.uid, scope: 'foo', redirect_uri: 'http://unsafe-website.com/callback' }
- expect(response).to redirect_to(oauth_authorization_url(client_id: 'client-123',
- response_type: 'code',
- scope: 'api',
- redirect_uri: oauth_jira_dvcs_callback_url))
+ expect(response).to have_gitlab_http_status(:forbidden)
end
end
@@ -47,7 +63,7 @@ RSpec.describe Oauth::JiraDvcs::AuthorizationsController do
double(status: :ok, body: { 'access_token' => 'fake-123', 'scope' => 'foo', 'token_type' => 'bar' })
end
- post :access_token, params: { code: 'code-123', client_id: 'client-123', client_secret: 'secret-123' }
+ post :access_token, params: { code: 'code-123', client_id: application.uid, client_secret: 'secret-123' }
expect(response.body).to eq('access_token=fake-123&scope=foo&token_type=bar')
end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index f35d8af165e..c91aa562a85 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -247,7 +247,9 @@ RSpec.describe Projects::ArtifactsController, feature_category: :build_artifacts
let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
before do
- create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: job)
+ allow_next_found_instance_of(Ci::Build) do |build|
+ allow(build).to receive(:debug_mode?).and_return(true)
+ end
end
context 'when the user does not have update_build permissions' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index cc4bca8d122..2e29d87dadd 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -630,40 +630,12 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
expect(json_response['lines'].count).to be_positive
end
- context 'when CI_DEBUG_TRACE enabled' do
- let!(:variable) { create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true') }
-
- context 'with proper permissions on a project' do
- let(:user) { developer }
-
- before do
- sign_in(user)
- end
-
- it 'returns response ok' do
- get_trace
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'without proper permissions for debug logging' do
- let(:user) { guest }
-
- before do
- sign_in(user)
- end
-
- it 'returns response forbidden' do
- get_trace
-
- expect(response).to have_gitlab_http_status(:forbidden)
+ context 'when debug_mode? is enabled' do
+ before do
+ allow_next_found_instance_of(Ci::Build) do |build|
+ allow(build).to receive(:debug_mode?).and_return(true)
end
end
- end
-
- context 'when CI_DEBUG_SERVICES enabled' do
- let!(:variable) { create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: 'true') }
context 'with proper permissions on a project' do
let(:user) { developer }
@@ -1242,10 +1214,10 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
context 'when CI_DEBUG_TRACE and/or CI_DEBUG_SERVICES are enabled' do
using RSpec::Parameterized::TableSyntax
where(:ci_debug_trace, :ci_debug_services) do
- 'true' | 'true'
- 'true' | 'false'
- 'false' | 'true'
- 'false' | 'false'
+ true | true
+ true | false
+ false | true
+ false | false
end
with_them do
@@ -1278,7 +1250,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state, featu
it 'returns response forbidden if dev mode enabled' do
response = subject
- if ci_debug_trace == 'true' || ci_debug_services == 'true'
+ if ci_debug_trace || ci_debug_services
expect(response).to have_gitlab_http_status(:forbidden)
else
expect(response).to have_gitlab_http_status(:ok)
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index b2a29c88b68..97b0e6d5c48 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -47,12 +47,14 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
def push_code_contribution
event = create(:push_event, project: contributed_project, author: user)
- create(:push_event_payload,
- event: event,
- commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
- commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
- commit_count: 3,
- ref: 'master')
+ create(
+ :push_event_payload,
+ event: event,
+ commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce',
+ commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2',
+ commit_count: 3,
+ ref: 'master'
+ )
end
def note_comment_contribution
@@ -70,162 +72,279 @@ RSpec.describe 'Contributions Calendar', :js, feature_category: :user_profile do
find('#js-overview .user-calendar-activities', visible: visible).text
end
- before do
- stub_feature_flags(profile_tabs_vue: false)
- sign_in user
- end
-
- describe 'calendar day selection' do
+ shared_context 'when user page is visited' do
before do
visit user.username
- page.find('.js-overview-tab a').click
+ page.click_link('Overview')
wait_for_requests
end
+ end
- it 'displays calendar' do
- expect(find('#js-overview')).to have_css('.js-contrib-calendar')
+ context 'with `profile_tabs_vue` feature flag disabled' do
+ before do
+ stub_feature_flags(profile_tabs_vue: false)
+ sign_in user
end
- describe 'select calendar day' do
- let(:cells) { page.all('#js-overview .user-contrib-cell') }
+ describe 'calendar day selection' do
+ include_context 'when user page is visited'
- before do
- cells[0].click
- wait_for_requests
- @first_day_activities = selected_day_activities
+ it 'displays calendar' do
+ expect(find('#js-overview')).to have_css('.js-contrib-calendar')
end
- it 'displays calendar day activities' do
- expect(selected_day_activities).not_to be_empty
- end
+ describe 'select calendar day' do
+ let(:cells) { page.all('#js-overview .user-contrib-cell') }
- describe 'select another calendar day' do
before do
- cells[1].click
+ cells[0].click
wait_for_requests
end
- it 'displays different calendar day activities' do
- expect(selected_day_activities).not_to eq(@first_day_activities)
+ it 'displays calendar day activities' do
+ expect(selected_day_activities).not_to be_empty
end
- end
- describe 'deselect calendar day' do
- before do
- cells[0].click
- wait_for_requests
- cells[0].click
+ describe 'select another calendar day' do
+ it 'displays different calendar day activities' do
+ first_day_activities = selected_day_activities
+
+ cells[1].click
+ wait_for_requests
+
+ expect(selected_day_activities).not_to eq(first_day_activities)
+ end
end
- it 'hides calendar day activities' do
- expect(selected_day_activities(visible: false)).to be_empty
+ describe 'deselect calendar day' do
+ before do
+ cells[0].click
+ wait_for_requests
+ cells[0].click
+ end
+
+ it 'hides calendar day activities' do
+ expect(selected_day_activities(visible: false)).to be_empty
+ end
end
end
end
- end
- shared_context 'visit user page' do
- before do
- visit user.username
- page.find('.js-overview-tab a').click
- wait_for_requests
- end
- end
+ describe 'calendar daily activities' do
+ shared_examples 'a day with activity' do |contribution_count:|
+ include_context 'when user page is visited'
- describe 'calendar daily activities' do
- shared_examples 'a day with activity' do |contribution_count:|
- include_context 'visit user page'
+ it 'displays calendar activity square for 1 contribution', :sidekiq_inline do
+ expect(find('#js-overview')).to have_selector(get_cell_level_selector(contribution_count), count: 1)
- it 'displays calendar activity square for 1 contribution', :sidekiq_might_not_need_inline do
- expect(find('#js-overview')).to have_selector(get_cell_level_selector(contribution_count), count: 1)
+ today = Date.today.strftime(date_format)
+ expect(find('#js-overview')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
+ end
+ end
+
+ describe '1 issue and 1 work item creation calendar activity' do
+ before do
+ Issues::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: issue_params,
+ spam_params: nil
+ ).execute
+ WorkItems::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: { title: 'new task' },
+ spam_params: nil
+ ).execute
+ end
- today = Date.today.strftime(date_format)
- expect(find('#js-overview')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
+ it_behaves_like 'a day with activity', contribution_count: 2
+
+ describe 'issue title is shown on activity page' do
+ include_context 'when user page is visited'
+
+ it 'displays calendar activity log', :sidekiq_inline do
+ expect(all('#js-overview .overview-content-list .event-target-title').map(&:text)).to contain_exactly(
+ match(/#{issue_title}/),
+ match(/new task/)
+ )
+ end
+ end
end
- end
- describe '1 issue and 1 work item creation calendar activity' do
- before do
- Issues::CreateService.new(container: contributed_project, current_user: user, params: issue_params, spam_params: nil).execute
- WorkItems::CreateService.new(
- container: contributed_project,
- current_user: user,
- params: { title: 'new task' },
- spam_params: nil
- ).execute
+ describe '1 comment calendar activity' do
+ before do
+ note_comment_contribution
+ end
+
+ it_behaves_like 'a day with activity', contribution_count: 1
+ end
+
+ describe '10 calendar activities' do
+ before do
+ 10.times { push_code_contribution }
+ end
+
+ it_behaves_like 'a day with activity', contribution_count: 10
end
- it_behaves_like 'a day with activity', contribution_count: 2
+ describe 'calendar activity on two days' do
+ before do
+ push_code_contribution
+
+ travel_to(Date.yesterday) do
+ Issues::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: issue_params,
+ spam_params: nil
+ ).execute
+ end
+ end
+
+ include_context 'when user page is visited'
- describe 'issue title is shown on activity page' do
- include_context 'visit user page'
+ it 'displays calendar activity squares for both days', :sidekiq_inline do
+ expect(find('#js-overview')).to have_selector(get_cell_level_selector(1), count: 2)
+ end
+
+ it 'displays calendar activity square for yesterday', :sidekiq_inline do
+ yesterday = Date.yesterday.strftime(date_format)
+ expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ end
- it 'displays calendar activity log', :sidekiq_inline do
- expect(all('#js-overview .overview-content-list .event-target-title').map(&:text)).to contain_exactly(
- match(/#{issue_title}/),
- match(/new task/)
- )
+ it 'displays calendar activity square for today' do
+ today = Date.today.strftime(date_format)
+ expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, today), count: 1)
end
end
end
- describe '1 comment calendar activity' do
- before do
- note_comment_contribution
+ describe 'on smaller screens' do
+ shared_examples 'hidden activity calendar' do
+ include_context 'when user page is visited'
+
+ it 'hides the activity calender' do
+ expect(find('#js-overview')).not_to have_css('.js-contrib-calendar')
+ end
end
- it_behaves_like 'a day with activity', contribution_count: 1
- end
+ context 'when screen size is xs' do
+ before do
+ resize_screen_xs
+ end
- describe '10 calendar activities' do
- before do
- 10.times { push_code_contribution }
+ it_behaves_like 'hidden activity calendar'
end
+ end
+ end
- it_behaves_like 'a day with activity', contribution_count: 10
+ context 'with `profile_tabs_vue` feature flag enabled' do
+ before do
+ sign_in user
end
- describe 'calendar activity on two days' do
- before do
- push_code_contribution
+ include_context 'when user page is visited'
- travel_to(Date.yesterday) do
- Issues::CreateService.new(container: contributed_project, current_user: user, params: issue_params, spam_params: nil).execute
+ it 'displays calendar' do
+ expect(page).to have_css('[data-testid="contrib-calendar"]')
+ end
+
+ describe 'calendar daily activities' do
+ shared_examples 'a day with activity' do |contribution_count:|
+ include_context 'when user page is visited'
+
+ it 'displays calendar activity square for 1 contribution', :sidekiq_inline do
+ expect(page).to have_selector(get_cell_level_selector(contribution_count), count: 1)
+
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1)
end
end
- include_context 'visit user page'
- it 'displays calendar activity squares for both days', :sidekiq_might_not_need_inline do
- expect(find('#js-overview')).to have_selector(get_cell_level_selector(1), count: 2)
+ describe '1 issue and 1 work item creation calendar activity' do
+ before do
+ Issues::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: issue_params,
+ spam_params: nil
+ ).execute
+ WorkItems::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: { title: 'new task' },
+ spam_params: nil
+ ).execute
+ end
+
+ it_behaves_like 'a day with activity', contribution_count: 2
end
- it 'displays calendar activity square for yesterday', :sidekiq_might_not_need_inline do
- yesterday = Date.yesterday.strftime(date_format)
- expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ describe '1 comment calendar activity' do
+ before do
+ note_comment_contribution
+ end
+
+ it_behaves_like 'a day with activity', contribution_count: 1
end
- it 'displays calendar activity square for today' do
- today = Date.today.strftime(date_format)
- expect(find('#js-overview')).to have_selector(get_cell_date_selector(1, today), count: 1)
+ describe '10 calendar activities' do
+ before do
+ 10.times { push_code_contribution }
+ end
+
+ it_behaves_like 'a day with activity', contribution_count: 10
end
- end
- end
- describe 'on smaller screens' do
- shared_examples 'hidden activity calendar' do
- include_context 'visit user page'
+ describe 'calendar activity on two days' do
+ before do
+ push_code_contribution
+
+ travel_to(Date.yesterday) do
+ Issues::CreateService.new(
+ container: contributed_project,
+ current_user: user,
+ params: issue_params,
+ spam_params: nil
+ ).execute
+ end
+ end
+
+ include_context 'when user page is visited'
+
+ it 'displays calendar activity squares for both days', :sidekiq_inline do
+ expect(page).to have_selector(get_cell_level_selector(1), count: 2)
+ end
+
+ it 'displays calendar activity square for yesterday', :sidekiq_inline do
+ yesterday = Date.yesterday.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1)
+ end
- it 'hides the activity calender' do
- expect(find('#js-overview')).not_to have_css('.js-contrib-calendar')
+ it 'displays calendar activity square for today' do
+ today = Date.today.strftime(date_format)
+ expect(page).to have_selector(get_cell_date_selector(1, today), count: 1)
+ end
end
end
- context 'size xs' do
- before do
- resize_screen_xs
+ describe 'on smaller screens' do
+ shared_examples 'hidden activity calendar' do
+ include_context 'when user page is visited'
+
+ it 'hides the activity calender' do
+ expect(page).not_to have_css('[data-testid="contrib-calendar"]')
+ end
end
- it_behaves_like 'hidden activity calendar'
+ context 'when screen size is xs' do
+ before do
+ resize_screen_xs
+ end
+
+ it_behaves_like 'hidden activity calendar'
+ end
end
end
end
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index c3c0043a6ef..dce86c9f0a4 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -149,109 +149,44 @@ RSpec.describe 'Project Jobs Permissions', feature_category: :projects do
end
end
- context 'with CI_DEBUG_TRACE' do
- let_it_be(:ci_instance_variable) { create(:ci_instance_variable, key: 'CI_DEBUG_TRACE') }
-
- describe 'trace endpoint' do
- let_it_be(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
-
- where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code) do
- true | 'developer' | true | 200
- true | 'guest' | true | 403
- true | 'developer' | false | 200
- true | 'guest' | false | 200
- false | 'developer' | true | 200
- false | 'guest' | true | 403
- false | 'developer' | false | 200
- false | 'guest' | false | 403
- end
-
- with_them do
- before do
- ci_instance_variable.update!(value: ci_debug_trace)
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
- end
-
- it 'renders trace to authorized users' do
- visit trace_project_job_path(project, job)
-
- expect(status_code).to eq(expected_status_code)
- end
- end
+ describe 'debug_mode' do
+ let(:job) { create(:ci_build, :trace_artifact, pipeline: pipeline) }
+
+ where(:public_builds, :user_project_role, :debug_mode, :expected_status_code, :expected_msg) do
+ true | 'developer' | true | 200 | ''
+ true | 'guest' | true | 403 | 'You must have developer or higher permissions'
+ true | nil | true | 404 | 'Page Not Found Make sure the address is correct'
+ true | 'developer' | false | 200 | ''
+ true | 'guest' | false | 200 | ''
+ true | nil | false | 404 | 'Page Not Found Make sure the address is correct'
+ false | 'developer' | true | 200 | ''
+ false | 'guest' | true | 403 | 'You must have developer or higher permissions'
+ false | nil | true | 404 | 'Page Not Found Make sure the address is correct'
+ false | 'developer' | false | 200 | ''
+ false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
+ false | nil | false | 404 | 'Page Not Found Make sure the address is correct'
end
- describe 'raw page' do
- let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
-
- where(:public_builds, :user_project_role, :ci_debug_trace, :expected_status_code, :expected_msg) do
- true | 'developer' | true | 200 | nil
- true | 'guest' | true | 403 | 'You must have developer or higher permissions'
- true | 'developer' | false | 200 | nil
- true | 'guest' | false | 200 | nil
- false | 'developer' | true | 200 | nil
- false | 'guest' | true | 403 | 'You must have developer or higher permissions'
- false | 'developer' | false | 200 | nil
- false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
- end
-
- with_them do
- before do
- ci_instance_variable.update!(value: ci_debug_trace)
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
- end
-
- it 'renders raw trace to authorized users' do
- visit raw_project_job_path(project, job)
-
- expect(status_code).to eq(expected_status_code)
- expect(page).to have_content(expected_msg)
+ with_them do
+ before do
+ project.update!(public_builds: public_builds)
+ user_project_role && project.add_role(user, user_project_role)
+ allow_next_found_instance_of(Ci::Build) do |build|
+ allow(build).to receive(:debug_mode?).and_return(debug_mode)
end
end
- end
- end
-
- context 'with CI_DEBUG_SERVICES' do
- let_it_be(:ci_instance_variable) { create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES') }
-
- describe 'trace endpoint and raw page' do
- let_it_be(:job) { create(:ci_build, :running, :coverage, :trace_artifact, pipeline: pipeline) }
-
- where(:public_builds, :user_project_role, :ci_debug_services, :expected_status_code, :expected_msg) do
- true | 'developer' | true | 200 | nil
- true | 'guest' | true | 403 | 'You must have developer or higher permissions'
- true | nil | true | 404 | 'Page Not Found Make sure the address is correct'
- true | 'developer' | false | 200 | nil
- true | 'guest' | false | 200 | nil
- true | nil | false | 404 | 'Page Not Found Make sure the address is correct'
- false | 'developer' | true | 200 | nil
- false | 'guest' | true | 403 | 'You must have developer or higher permissions'
- false | nil | true | 404 | 'Page Not Found Make sure the address is correct'
- false | 'developer' | false | 200 | nil
- false | 'guest' | false | 403 | 'The current user is not authorized to access the job log'
- false | nil | false | 404 | 'Page Not Found Make sure the address is correct'
- end
- with_them do
- before do
- ci_instance_variable.update!(value: ci_debug_services)
- project.update!(public_builds: public_builds)
- user_project_role && project.add_role(user, user_project_role)
- end
-
- it 'renders trace to authorized users' do
- visit trace_project_job_path(project, job)
+ it 'renders trace to authorized users' do
+ visit trace_project_job_path(project, job)
- expect(status_code).to eq(expected_status_code)
- end
+ expect(status_code).to eq(expected_status_code)
+ end
- it 'renders raw trace to authorized users' do
- visit raw_project_job_path(project, job)
+ it 'renders raw trace to authorized users' do
+ visit raw_project_job_path(project, job)
- expect(status_code).to eq(expected_status_code)
- expect(page).to have_content(expected_msg)
- end
+ expect(status_code).to eq(expected_status_code)
+ expect(page).to have_content(expected_msg)
end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 61be90b267a..792a14e3064 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -148,15 +148,6 @@ RSpec.describe NotesFinder do
expect(notes.count).to eq(1)
end
- it 'finds notes on personal snippets' do
- note = create(:note_on_personal_snippet)
- params = { project: project, target_type: 'personal_snippet', target_id: note.noteable_id }
-
- notes = described_class.new(user, params).execute
-
- expect(notes.count).to eq(1)
- end
-
it 'raises an exception for an invalid target_type' do
params[:target_type] = 'invalid'
expect { described_class.new(user, params).execute }.to raise_error("invalid target_type '#{params[:target_type]}'")
@@ -190,6 +181,44 @@ RSpec.describe NotesFinder do
expect { described_class.new(user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
+
+ context 'when targeting personal_snippet' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:author) { create(:user) }
+ let(:user) { create(:user, email: 'foo@baz.com') }
+ let(:admin) { create(:admin) }
+
+ where(:snippet_visibility, :current_user, :access) do
+ Snippet::PRIVATE | ref(:author) | true
+ Snippet::PRIVATE | ref(:admin) | true
+ Snippet::PRIVATE | ref(:user) | false
+ Snippet::PUBLIC | ref(:author) | true
+ Snippet::PUBLIC | ref(:user) | true
+ end
+
+ with_them do
+ let(:personal_snippet) { create(:personal_snippet, author: author, visibility_level: snippet_visibility) }
+ let(:note) { create(:note, noteable: personal_snippet) }
+ let(:params) { { project: project, target_type: 'personal_snippet', target_id: note.noteable.id } }
+
+ subject(:notes) do
+ described_class.new(current_user, params).execute
+ end
+
+ before do
+ allow(admin).to receive(:can_read_all_resources?).and_return(true)
+ end
+
+ it 'returns the proper access' do
+ if access
+ expect(notes.count).to eq(1)
+ else
+ expect { notes }.to raise_error(::ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+ end
end
context 'for explicit target' do
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 9c9a04a4df5..48880ec2c1f 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -231,22 +231,31 @@ RSpec.describe SnippetsFinder do
context 'filter by snippet type' do
context 'when filtering by only_personal snippet', :enable_admin_mode do
- it 'returns only personal snippet' do
+ let!(:admin_private_personal_snippet) { create(:personal_snippet, :private, author: admin) }
+ let(:user_without_snippets) { create :user }
+
+ it 'returns all personal snippets for the admin' do
snippets = described_class.new(admin, only_personal: true).execute
+ expect(snippets).to contain_exactly(admin_private_personal_snippet,
+ private_personal_snippet,
+ internal_personal_snippet,
+ public_personal_snippet)
+ end
+
+ it 'returns only personal snippets visible by user' do
+ snippets = described_class.new(user, only_personal: true).execute
+
expect(snippets).to contain_exactly(private_personal_snippet,
internal_personal_snippet,
public_personal_snippet)
end
- end
- context 'when filtering by only_project snippet', :enable_admin_mode do
- it 'returns only project snippet' do
- snippets = described_class.new(admin, only_project: true).execute
+ it 'returns only internal or public personal snippets for user without snippets' do
+ snippets = described_class.new(user_without_snippets, only_personal: true).execute
- expect(snippets).to contain_exactly(private_project_snippet,
- internal_project_snippet,
- public_project_snippet)
+ expect(snippets).to contain_exactly(internal_personal_snippet,
+ public_personal_snippet)
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/tag.json b/spec/fixtures/api/schemas/public_api/v4/tag.json
index bb0190955f0..71e6c0ec035 100644
--- a/spec/fixtures/api/schemas/public_api/v4/tag.json
+++ b/spec/fixtures/api/schemas/public_api/v4/tag.json
@@ -3,8 +3,7 @@
"required" : [
"name",
"message",
- "commit",
- "release"
+ "commit"
],
"properties" : {
"name": { "type": "string" },
diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
index 1c2e7033488..58e074b5b2d 100644
--- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
@@ -1,6 +1,5 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { ApolloMutation } from 'vue-apollo';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
@@ -9,7 +8,6 @@ import DesignNote from '~/design_management/components/design_notes/design_note.
import DesignNoteSignedOut from '~/design_management/components/design_notes/design_note_signed_out.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
-import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import destroyNoteMutation from '~/design_management/graphql/mutations/destroy_note.mutation.graphql';
@@ -40,18 +38,7 @@ describe('Design discussions component', () => {
const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
const findResolveLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');
- const findApolloMutation = () => wrapper.findComponent(ApolloMutation);
- const mutationVariables = {
- mutation: createNoteMutation,
- variables: {
- input: {
- noteableId: 'noteable-id',
- body: 'test',
- discussionId: '0',
- },
- },
- };
const registerPath = '/users/sign_up?redirect_to_referer=yes';
const signInPath = '/users/sign_in?redirect_to_referer=yes';
const mutate = jest.fn().mockResolvedValue({ data: { createNote: { errors: [] } } });
@@ -222,7 +209,7 @@ describe('Design discussions component', () => {
it('emit todo:toggle when discussion is resolved', async () => {
createComponent({
props: { discussionWithOpenForm: defaultMockDiscussion.id },
- data: { discussionComment: 'test', isFormRendered: true },
+ data: { isFormRendered: true },
});
findResolveButton().trigger('click');
findReplyForm().vm.$emit('submitForm');
@@ -275,16 +262,14 @@ describe('Design discussions component', () => {
expect(findReplyForm().exists()).toBe(true);
});
- it('calls mutation on submitting form and closes the form', async () => {
+ it('closes the form when note submit mutation is completed', async () => {
createComponent({
props: { discussionWithOpenForm: defaultMockDiscussion.id },
- data: { discussionComment: 'test', isFormRendered: true },
+ data: { isFormRendered: true },
});
- findReplyForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ findReplyForm().vm.$emit('note-submit-complete', { data: { createNote: {} } });
- await mutate();
await nextTick();
expect(findReplyForm().exists()).toBe(false);
@@ -293,14 +278,12 @@ describe('Design discussions component', () => {
it('clears the discussion comment on closing comment form', async () => {
createComponent({
props: { discussionWithOpenForm: defaultMockDiscussion.id },
- data: { discussionComment: 'test', isFormRendered: true },
+ data: { isFormRendered: true },
});
await nextTick();
findReplyForm().vm.$emit('cancel-form');
- expect(wrapper.vm.discussionComment).toBe('');
-
await nextTick();
expect(findReplyForm().exists()).toBe(false);
});
@@ -345,7 +328,7 @@ describe('Design discussions component', () => {
it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
createComponent({
props: { discussionWithOpenForm: defaultMockDiscussion.id },
- data: { discussionComment: 'test', isFormRendered: true },
+ data: { isFormRendered: true },
});
findResolveButton().trigger('click');
findReplyForm().vm.$emit('submitForm');
@@ -380,7 +363,7 @@ describe('Design discussions component', () => {
},
discussionWithOpenForm: defaultMockDiscussion.id,
},
- data: { discussionComment: 'test', isFormRendered: true },
+ data: { isFormRendered: true },
});
});
@@ -392,10 +375,6 @@ describe('Design discussions component', () => {
expect(findReplyPlaceholder().exists()).toBe(false);
});
- it('does not render apollo-mutation component', () => {
- expect(findApolloMutation().exists()).toBe(false);
- });
-
it('renders design-note-signed-out component', () => {
expect(findDesignNoteSignedOut().exists()).toBe(true);
expect(findDesignNoteSignedOut().props()).toMatchObject({
diff --git a/spec/frontend/design_management/components/design_notes/design_note_spec.js b/spec/frontend/design_management/components/design_notes/design_note_spec.js
index ba4ee3cbe03..eda231fc468 100644
--- a/spec/frontend/design_management/components/design_notes/design_note_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_note_spec.js
@@ -168,15 +168,21 @@ describe('Design note component', () => {
expect(findNoteContent().exists()).toBe(true);
});
- it('calls a mutation on submit-form event and hides a form', async () => {
- findReplyForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalled();
+ it('hides a form after update mutation is completed', async () => {
+ findReplyForm().vm.$emit('note-submit-complete', { data: { updateNote: { errors: [] } } });
- await mutate();
await nextTick();
expect(findReplyForm().exists()).toBe(false);
expect(findNoteContent().exists()).toBe(true);
});
+
+ it('emits error on failure', async () => {
+ const mockError = 'error';
+ findReplyForm().vm.$emit('note-submit-failure', mockError);
+
+ await nextTick();
+ expect(wrapper.emitted('error')).toEqual([[mockError]]);
+ });
});
});
diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
index f4d4f9cf896..a1da2ec1300 100644
--- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
@@ -1,8 +1,14 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Autosave from '~/autosave';
+import waitForPromises from 'helpers/wait_for_promises';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
+import {
+ mockNoteSubmitSuccessMutationResponse,
+ mockNoteSubmitFailureMutationResponse,
+} from '../../mock_data/apollo_mock';
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
jest.mock('~/autosave');
@@ -15,15 +21,45 @@ describe('Design reply form component', () => {
const findSubmitButton = () => wrapper.findComponent({ ref: 'submitButton' });
const findCancelButton = () => wrapper.findComponent({ ref: 'cancelButton' });
- function createComponent(props = {}, mountOptions = {}) {
+ const mockNoteableId = 'gid://gitlab/DesignManagement::Design/6';
+ const mockComment = 'New comment';
+ const mockDiscussionId = 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8';
+ const createNoteMutationData = {
+ mutation: createNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ body: mockComment,
+ },
+ },
+ };
+
+ const ctrlKey = {
+ ctrlKey: true,
+ };
+ const metaKey = {
+ metaKey: true,
+ };
+ const mutationHandler = jest.fn().mockResolvedValue();
+
+ function createComponent({ props = {}, mountOptions = {}, mutation = mutationHandler } = {}) {
wrapper = mount(DesignReplyForm, {
propsData: {
+ designNoteMutation: createNoteMutation,
+ noteableId: mockNoteableId,
+ markdownDocsPath: 'path/to/markdown/docs',
+ markdownPreviewPath: 'path/to/markdown/preview',
value: '',
- isSaving: false,
- noteableId: 'gid://gitlab/DesignManagement::Design/6',
...props,
},
...mountOptions,
+ mocks: {
+ $apollo: {
+ mutate: mutation,
+ },
+ },
});
}
@@ -40,7 +76,7 @@ describe('Design reply form component', () => {
it('textarea has focus after component mount', () => {
// We need to attach to document, so that `document.activeElement` is properly set in jsdom
- createComponent({}, { attachTo: document.body });
+ createComponent({ mountOptions: { attachTo: document.body } });
expect(findTextarea().element).toEqual(document.activeElement);
});
@@ -64,7 +100,7 @@ describe('Design reply form component', () => {
});
it('renders button text as "Save comment" when creating a comment', () => {
- createComponent({ isNewComment: false });
+ createComponent({ props: { isNewComment: false } });
expect(findSubmitButton().html()).toMatchSnapshot();
});
@@ -76,7 +112,7 @@ describe('Design reply form component', () => {
`(
'initializes autosave support on discussion with proper key',
async ({ discussionId, shortDiscussionId }) => {
- createComponent({ discussionId });
+ createComponent({ props: { discussionId } });
await nextTick();
expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [
@@ -88,32 +124,24 @@ describe('Design reply form component', () => {
);
describe('when form has no text', () => {
- beforeEach(() => {
- createComponent({
- value: '',
- });
+ beforeEach(async () => {
+ createComponent();
+ await nextTick();
});
it('submit button is disabled', () => {
expect(findSubmitButton().attributes().disabled).toBe('disabled');
});
- it('does not emit submitForm event on textarea ctrl+enter keydown', async () => {
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
- });
-
- await nextTick();
- expect(wrapper.emitted('submit-form')).toBeUndefined();
- });
-
- it('does not emit submitForm event on textarea meta+enter keydown', async () => {
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
- });
+ it.each`
+ key | keyData
+ ${'ctrl'} | ${ctrlKey}
+ ${'meta'} | ${metaKey}
+ `('does not perform mutation on textarea $key+enter keydown', async ({ keyData }) => {
+ findTextarea().trigger('keydown.enter', keyData);
await nextTick();
- expect(wrapper.emitted('submit-form')).toBeUndefined();
+ expect(mutationHandler).not.toHaveBeenCalled();
});
it('emits cancelForm event on pressing escape button on textarea', () => {
@@ -129,118 +157,148 @@ describe('Design reply form component', () => {
});
});
- describe('when form has text', () => {
- beforeEach(() => {
- createComponent({
- value: 'test',
- });
- });
-
+ describe('when the form has text', () => {
it('submit button is enabled', () => {
+ createComponent({ props: { value: mockComment } });
expect(findSubmitButton().attributes().disabled).toBeUndefined();
});
- it('emits submitForm event on Comment button click', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ it('calls a mutation on submit button click event', async () => {
+ const mockMutationVariables = {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ };
+ const successfulMutation = jest.fn().mockResolvedValue(mockNoteSubmitSuccessMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ mutationVariables: mockMutationVariables,
+ value: mockComment,
+ },
+ mutation: successfulMutation,
+ });
findSubmitButton().vm.$emit('click');
await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
+ expect(successfulMutation).toHaveBeenCalledWith(createNoteMutationData);
- it('emits submitForm event on textarea ctrl+enter keydown', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ await waitForPromises();
+ expect(wrapper.emitted('note-submit-complete')).toEqual([
+ [mockNoteSubmitSuccessMutationResponse],
+ ]);
+ });
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
+ it.each`
+ key | keyData
+ ${'ctrl'} | ${ctrlKey}
+ ${'meta'} | ${metaKey}
+ `('does perform mutation on textarea $key+enter keydown', async ({ keyData }) => {
+ const mockMutationVariables = {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ };
+ const successfulMutation = jest.fn().mockResolvedValue(mockNoteSubmitSuccessMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ mutationVariables: mockMutationVariables,
+ value: mockComment,
+ },
+ mutation: successfulMutation,
});
+ findTextarea().trigger('keydown.enter', keyData);
+
await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
+ expect(successfulMutation).toHaveBeenCalledWith(createNoteMutationData);
- it('emits submitForm event on textarea meta+enter keydown', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ await waitForPromises();
+ expect(wrapper.emitted('note-submit-complete')).toEqual([
+ [mockNoteSubmitSuccessMutationResponse],
+ ]);
+ });
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
+ it('emits error when mutation fails', async () => {
+ const mockMutationVariables = {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ };
+ const failedMutation = jest.fn().mockRejectedValue(mockNoteSubmitFailureMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ mutationVariables: mockMutationVariables,
+ value: mockComment,
+ },
+ mutation: failedMutation,
});
- await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
-
- it('emits input event on changing textarea content', async () => {
- findTextarea().setValue('test2');
+ findSubmitButton().vm.$emit('click');
- await nextTick();
- expect(wrapper.emitted('input')).toEqual([['test2']]);
+ await waitForPromises();
+ expect(wrapper.emitted('note-submit-failure')).toEqual([
+ [mockNoteSubmitFailureMutationResponse],
+ ]);
});
it('emits cancelForm event on Escape key if text was not changed', () => {
+ createComponent();
+
findTextarea().trigger('keyup.esc');
expect(wrapper.emitted('cancel-form')).toHaveLength(1);
});
it('opens confirmation modal on Escape key when text has changed', async () => {
- wrapper.setProps({ value: 'test2' });
+ createComponent();
+
+ findTextarea().setValue(mockComment);
await nextTick();
findTextarea().trigger('keyup.esc');
- expect(confirmAction).toHaveBeenCalled();
- });
-
- it('emits cancelForm event on Cancel button click if text was not changed', () => {
- findCancelButton().trigger('click');
-
- expect(wrapper.emitted('cancel-form')).toHaveLength(1);
- });
-
- it('opens confirmation modal on Cancel button click when text has changed', async () => {
- wrapper.setProps({ value: 'test2' });
- await nextTick();
- findCancelButton().trigger('click');
expect(confirmAction).toHaveBeenCalled();
});
it('emits cancelForm event when confirmed', async () => {
confirmAction.mockResolvedValueOnce(true);
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
- wrapper.setProps({ value: 'test3' });
- await nextTick();
+ createComponent({ props: { value: mockComment } });
+ findTextarea().setValue('Comment changed');
- findTextarea().trigger('keyup.esc');
await nextTick();
+ findTextarea().trigger('keyup.esc');
expect(confirmAction).toHaveBeenCalled();
- await nextTick();
+ await waitForPromises();
expect(wrapper.emitted('cancel-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
});
- it("doesn't emit cancelForm event when not confirmed", async () => {
+ it('does not emit cancelForm event when not confirmed', async () => {
confirmAction.mockResolvedValueOnce(false);
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
- wrapper.setProps({ value: 'test3' });
+ createComponent({ props: { value: mockComment } });
+ findTextarea().setValue('Comment changed');
await nextTick();
findTextarea().trigger('keyup.esc');
await nextTick();
expect(confirmAction).toHaveBeenCalled();
- await nextTick();
+ await waitForPromises();
expect(wrapper.emitted('cancel-form')).toBeUndefined();
- expect(autosaveResetSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when component is destroyed', () => {
+ it('calls autosave.reset', async () => {
+ const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ createComponent();
+ await wrapper.destroy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/design_management/mock_data/apollo_mock.js b/spec/frontend/design_management/mock_data/apollo_mock.js
index 2a43b5debee..a90bbe19a4a 100644
--- a/spec/frontend/design_management/mock_data/apollo_mock.js
+++ b/spec/frontend/design_management/mock_data/apollo_mock.js
@@ -211,3 +211,109 @@ export const getDesignQueryResponse = {
},
},
};
+
+export const mockNoteSubmitSuccessMutationResponse = [
+ {
+ data: {
+ createNote: {
+ note: {
+ id: 'gid://gitlab/DiffNote/468',
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ body: 'New comment',
+ bodyHtml: "<p data-sourcepos='1:1-1:4' dir='auto'>asdd</p>",
+ createdAt: '2023-02-24T06:49:20Z',
+ resolved: false,
+ position: {
+ diffRefs: {
+ baseSha: 'f63ae53ed82d8765477c191383e1e6a000c10375',
+ startSha: 'f63ae53ed82d8765477c191383e1e6a000c10375',
+ headSha: 'f348c652f1a737151fc79047895e695fbe81464c',
+ __typename: 'DiffRefs',
+ },
+ x: 441,
+ y: 128,
+ height: 152,
+ width: 695,
+ __typename: 'DiffPosition',
+ },
+ userPermissions: {
+ adminNote: true,
+ repositionNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/DiffNote/459',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+ errors: [],
+ __typename: 'CreateNotePayload',
+ },
+ },
+ },
+];
+
+export const mockNoteSubmitFailureMutationResponse = [
+ {
+ errors: [
+ {
+ message:
+ 'Variable $input of type CreateNoteInput! was provided invalid value for bodyaa (Field is not defined on CreateNoteInput), body (Expected value to not be null)',
+ locations: [
+ {
+ line: 1,
+ column: 21,
+ },
+ ],
+ extensions: {
+ value: {
+ noteableId: 'gid://gitlab/DesignManagement::Design/10',
+ discussionId: 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8',
+ bodyaa: 'df',
+ },
+ problems: [
+ {
+ path: ['bodyaa'],
+ explanation: 'Field is not defined on CreateNoteInput',
+ },
+ {
+ path: ['body'],
+ explanation: 'Expected value to not be null',
+ },
+ ],
+ },
+ },
+ ],
+ },
+];
+
+export const mockCreateImageNoteDiffResponse = {
+ data: {
+ createImageDiffNote: {
+ note: {
+ author: {
+ username: '',
+ },
+ discussion: {},
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management/mock_data/project.js b/spec/frontend/design_management/mock_data/project.js
new file mode 100644
index 00000000000..e1c2057d8d1
--- /dev/null
+++ b/spec/frontend/design_management/mock_data/project.js
@@ -0,0 +1,17 @@
+import design from './design';
+
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ nodes: [
+ {
+ ...design,
+ },
+ ],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index a11463ab663..72b143d3789 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -1,15 +1,14 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
-import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import Api from '~/api';
import DesignPresentation from '~/design_management/components/design_presentation.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management/constants';
-import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql';
import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
+import getDesignQuery from '~/design_management/graphql/queries/get_design.query.graphql';
import DesignIndex from '~/design_management/pages/design/index.vue';
import createRouter from '~/design_management/router';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
@@ -17,6 +16,7 @@ import * as utils from '~/design_management/utils/design_management_utils';
import {
DESIGN_NOT_FOUND_ERROR,
DESIGN_VERSION_NOT_EXIST_ERROR,
+ ADD_IMAGE_DIFF_NOTE_ERROR,
} from '~/design_management/utils/error_messages';
import {
DESIGN_TRACKING_PAGE_NAME,
@@ -24,15 +24,22 @@ import {
DESIGN_SERVICE_PING_EVENT_TYPES,
} from '~/design_management/utils/tracking';
import { createAlert } from '~/flash';
+import * as cacheUpdate from '~/design_management/utils/cache_update';
import mockAllVersions from '../../mock_data/all_versions';
import design from '../../mock_data/design';
+import mockProject from '../../mock_data/project';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
+import { mockCreateImageNoteDiffResponse } from '../../mock_data/apollo_mock';
jest.mock('~/flash');
jest.mock('~/api.js');
const focusInput = jest.fn();
+const mockCacheObject = {
+ readQuery: jest.fn().mockReturnValue(mockProject),
+ writeQuery: jest.fn(),
+};
const mutate = jest.fn().mockResolvedValue();
const mockPageLayoutElement = {
classList: {
@@ -52,32 +59,13 @@ const mockDesignNoDiscussions = {
nodes: [],
},
};
-const newComment = 'new comment';
+
const annotationCoordinates = {
x: 10,
y: 10,
width: 100,
height: 100,
};
-const createDiscussionMutationVariables = {
- mutation: createImageDiffNoteMutation,
- update: expect.anything(),
- variables: {
- input: {
- body: newComment,
- noteableId: design.id,
- position: {
- headSha: 'headSha',
- baseSha: 'baseSha',
- startSha: 'startSha',
- paths: {
- newPath: 'full-design-path',
- },
- ...annotationCoordinates,
- },
- },
- },
-};
Vue.use(VueRouter);
@@ -85,8 +73,9 @@ describe('Design management design index page', () => {
let wrapper;
let router;
- const findDiscussionForm = () => wrapper.findComponent(DesignReplyForm);
+ const findDesignReplyForm = () => wrapper.findComponent(DesignReplyForm);
const findSidebar = () => wrapper.findComponent(DesignSidebar);
+ const findAlert = () => wrapper.findComponent(GlAlert);
const findDesignPresentation = () => wrapper.findComponent(DesignPresentation);
function createComponent(
@@ -95,7 +84,7 @@ describe('Design management design index page', () => {
data = {},
intialRouteOptions = {},
provide = {},
- stubs = { ApolloMutation, DesignSidebar, DesignReplyForm },
+ stubs = { DesignSidebar, DesignReplyForm },
} = {},
) {
const $apollo = {
@@ -105,6 +94,11 @@ describe('Design management design index page', () => {
},
},
mutate,
+ getClient() {
+ return {
+ cache: mockCacheObject,
+ };
+ },
};
router = createRouter();
@@ -216,7 +210,7 @@ describe('Design management design index page', () => {
findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
await nextTick();
- expect(findDiscussionForm().exists()).toBe(true);
+ expect(findDesignReplyForm().exists()).toBe(true);
});
it('keeps new discussion form focused', () => {
@@ -235,44 +229,70 @@ describe('Design management design index page', () => {
expect(focusInput).toHaveBeenCalled();
});
- it('sends a mutation on submitting form and closes form', async () => {
+ it('sends a update and closes the form when mutation is completed', async () => {
createComponent(
{ loading: false },
{
data: {
design,
annotationCoordinates,
- comment: newComment,
},
},
);
- findDiscussionForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
+ const addImageDiffNoteToStore = jest.spyOn(cacheUpdate, 'updateStoreAfterAddImageDiffNote');
+
+ const mockDesignVariables = {
+ fullPath: 'project-path',
+ iid: '1',
+ filenames: ['gid::/gitlab/Design/1'],
+ atVersion: null,
+ };
+
+ findDesignReplyForm().vm.$emit('note-submit-complete', mockCreateImageNoteDiffResponse);
await nextTick();
- await mutate({ variables: createDiscussionMutationVariables });
- expect(findDiscussionForm().exists()).toBe(false);
+ expect(addImageDiffNoteToStore).toHaveBeenCalledWith(
+ mockCacheObject,
+ mockCreateImageNoteDiffResponse.data.createImageDiffNote,
+ getDesignQuery,
+ mockDesignVariables,
+ );
+ expect(findDesignReplyForm().exists()).toBe(false);
});
- it('closes the form and clears the comment on canceling form', async () => {
+ it('sets error message when form submission fails', async () => {
createComponent(
{ loading: false },
{
data: {
design,
annotationCoordinates,
- comment: newComment,
},
},
);
- findDiscussionForm().vm.$emit('cancel-form');
+ findDesignReplyForm().vm.$emit('note-submit-failure');
+
+ await nextTick();
+ expect(findAlert().text()).toBe(ADD_IMAGE_DIFF_NOTE_ERROR);
+ });
+
+ it('closes the form and clears the comment on canceling form', async () => {
+ createComponent(
+ { loading: false },
+ {
+ data: {
+ design,
+ annotationCoordinates,
+ },
+ },
+ );
- expect(wrapper.vm.comment).toBe('');
+ findDesignReplyForm().vm.$emit('cancel-form');
await nextTick();
- expect(findDiscussionForm().exists()).toBe(false);
+ expect(findDesignReplyForm().exists()).toBe(false);
});
describe('with error', () => {
diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js
index 7560b733ae6..0699846e4bb 100644
--- a/spec/frontend/issues/show/components/title_spec.js
+++ b/spec/frontend/issues/show/components/title_spec.js
@@ -1,94 +1,99 @@
-import Vue, { nextTick } from 'vue';
+import { nextTick } from 'vue';
+import { GlButton } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import titleComponent from '~/issues/show/components/title.vue';
+import Title from '~/issues/show/components/title.vue';
import eventHub from '~/issues/show/event_hub';
-import Store from '~/issues/show/stores';
describe('Title component', () => {
- let vm;
- beforeEach(() => {
+ let wrapper;
+
+ const getTitleHeader = () => wrapper.findByTestId('issue-title');
+ const getEditButton = () => wrapper.findComponent(GlButton);
+
+ const createWrapper = (props) => {
setHTMLFixture(`<title />`);
- const Component = Vue.extend(titleComponent);
- const store = new Store({
- titleHtml: '',
- descriptionHtml: '',
- issuableRef: '',
- });
- vm = new Component({
+ wrapper = shallowMountExtended(Title, {
propsData: {
issuableRef: '#1',
titleHtml: 'Testing <img />',
titleText: 'Testing',
- showForm: false,
- formState: store.formState,
+ ...props,
},
- }).$mount();
- });
+ });
+ };
afterEach(() => {
resetHTMLFixture();
});
it('renders title HTML', () => {
- expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
- });
-
- it('updates page title when changing titleHtml', async () => {
- const spy = jest.spyOn(vm, 'setPageTitle');
- vm.titleHtml = 'test';
+ createWrapper();
- await nextTick();
- expect(spy).toHaveBeenCalled();
+ expect(getTitleHeader().element.innerHTML.trim()).toBe('Testing <img>');
});
it('animates title changes', async () => {
- vm.titleHtml = 'test';
+ createWrapper();
- await nextTick();
+ await wrapper.setProps({
+ titleHtml: 'test',
+ });
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
- jest.runAllTimers();
+ expect(getTitleHeader().classes('issue-realtime-pre-pulse')).toBe(true);
+ jest.runAllTimers();
await nextTick();
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
+ expect(getTitleHeader().classes('issue-realtime-trigger-pulse')).toBe(true);
});
it('updates page title after changing title', async () => {
- vm.titleHtml = 'changed';
- vm.titleText = 'changed';
+ createWrapper();
+
+ await wrapper.setProps({
+ titleHtml: 'changed',
+ titleText: 'changed',
+ });
- await nextTick();
expect(document.querySelector('title').textContent.trim()).toContain('changed');
});
describe('inline edit button', () => {
it('should not show by default', () => {
- expect(vm.$el.querySelector('.btn-edit')).toBeNull();
+ createWrapper();
+
+ expect(getEditButton().exists()).toBe(false);
});
it('should not show if canUpdate is false', () => {
- vm.showInlineEditButton = true;
- vm.canUpdate = false;
+ createWrapper({
+ showInlineEditButton: true,
+ canUpdate: false,
+ });
- expect(vm.$el.querySelector('.btn-edit')).toBeNull();
+ expect(getEditButton().exists()).toBe(false);
});
it('should show if showInlineEditButton and canUpdate', () => {
- vm.showInlineEditButton = true;
- vm.canUpdate = true;
+ createWrapper({
+ showInlineEditButton: true,
+ canUpdate: true,
+ });
- expect(vm.$el.querySelector('.btn-edit')).toBeDefined();
+ expect(getEditButton().exists()).toBe(true);
});
it('should trigger open.form event when clicked', async () => {
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- vm.showInlineEditButton = true;
- vm.canUpdate = true;
+ eventHub.$emit = jest.fn();
+
+ createWrapper({
+ showInlineEditButton: true,
+ canUpdate: true,
+ });
- await nextTick();
- vm.$el.querySelector('.btn-edit').click();
+ getEditButton().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
diff --git a/spec/frontend/profile/components/activity_calendar_spec.js b/spec/frontend/profile/components/activity_calendar_spec.js
new file mode 100644
index 00000000000..fb9dc7b22f7
--- /dev/null
+++ b/spec/frontend/profile/components/activity_calendar_spec.js
@@ -0,0 +1,120 @@
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import * as GitLabUIUtils from '@gitlab/ui/dist/utils';
+
+import ActivityCalendar from '~/profile/components/activity_calendar.vue';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { useFakeDate } from 'helpers/fake_date';
+import { userCalendarResponse } from '../mock_data';
+
+jest.mock('~/lib/utils/ajax_cache');
+jest.mock('@gitlab/ui/dist/utils');
+
+describe('ActivityCalendar', () => {
+ // Feb 21st, 2023
+ useFakeDate(2023, 1, 21);
+
+ let wrapper;
+
+ const defaultProvide = {
+ userCalendarPath: '/users/root/calendar.json',
+ utcOffset: '0',
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(ActivityCalendar, { provide: defaultProvide });
+ };
+
+ const mockSuccessfulApiRequest = () =>
+ AjaxCache.retrieve.mockResolvedValueOnce(userCalendarResponse);
+ const mockUnsuccessfulApiRequest = () => AjaxCache.retrieve.mockRejectedValueOnce();
+
+ const findCalendar = () => wrapper.findByTestId('contrib-calendar');
+
+ describe('when API request is loading', () => {
+ beforeEach(() => {
+ AjaxCache.retrieve.mockReturnValueOnce(new Promise(() => {}));
+ });
+
+ it('renders loading icon', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('when API request is successful', () => {
+ beforeEach(() => {
+ mockSuccessfulApiRequest();
+ });
+
+ it('renders the calendar', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ expect(wrapper.findByText(ActivityCalendar.i18n.calendarHint).exists()).toBe(true);
+ });
+
+ describe('when window is resized', () => {
+ it('re-renders the calendar', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ mockSuccessfulApiRequest();
+ window.innerWidth = 1200;
+ window.dispatchEvent(new Event('resize'));
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ expect(AjaxCache.retrieve).toHaveBeenCalledTimes(2);
+ });
+ });
+ });
+
+ describe('when API request is not successful', () => {
+ beforeEach(() => {
+ mockUnsuccessfulApiRequest();
+ });
+
+ it('renders error', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
+ });
+
+ describe('when retry button is clicked', () => {
+ it('retries API request', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ mockSuccessfulApiRequest();
+
+ await wrapper.findByRole('button', { name: ActivityCalendar.i18n.retry }).trigger('click');
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('when screen is extra small', () => {
+ beforeEach(() => {
+ GitLabUIUtils.GlBreakpointInstance.getBreakpointSize.mockReturnValueOnce('xs');
+ });
+
+ it('does not render the calendar', () => {
+ createComponent();
+
+ expect(findCalendar().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/profile/components/overview_tab_spec.js b/spec/frontend/profile/components/overview_tab_spec.js
index eb27515bca3..d4cb1dfd15d 100644
--- a/spec/frontend/profile/components/overview_tab_spec.js
+++ b/spec/frontend/profile/components/overview_tab_spec.js
@@ -3,6 +3,7 @@ import { GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import OverviewTab from '~/profile/components/overview_tab.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ActivityCalendar from '~/profile/components/activity_calendar.vue';
describe('OverviewTab', () => {
let wrapper;
@@ -16,4 +17,10 @@ describe('OverviewTab', () => {
expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Overview'));
});
+
+ it('renders `ActivityCalendar` component', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(ActivityCalendar).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/profile/mock_data.js b/spec/frontend/profile/mock_data.js
new file mode 100644
index 00000000000..7106ea84619
--- /dev/null
+++ b/spec/frontend/profile/mock_data.js
@@ -0,0 +1,22 @@
+export const userCalendarResponse = {
+ '2022-11-18': 13,
+ '2022-11-19': 21,
+ '2022-11-20': 14,
+ '2022-11-21': 15,
+ '2022-11-22': 20,
+ '2022-11-23': 21,
+ '2022-11-24': 15,
+ '2022-11-25': 14,
+ '2022-11-26': 16,
+ '2022-11-27': 13,
+ '2022-11-28': 4,
+ '2022-11-29': 1,
+ '2022-11-30': 1,
+ '2022-12-13': 1,
+ '2023-01-10': 3,
+ '2023-01-11': 2,
+ '2023-01-20': 1,
+ '2023-02-02': 1,
+ '2023-02-06': 2,
+ '2023-02-07': 2,
+};
diff --git a/spec/frontend/profile/utils_spec.js b/spec/frontend/profile/utils_spec.js
new file mode 100644
index 00000000000..43537afe169
--- /dev/null
+++ b/spec/frontend/profile/utils_spec.js
@@ -0,0 +1,15 @@
+import { getVisibleCalendarPeriod } from '~/profile/utils';
+import { CALENDAR_PERIOD_12_MONTHS, CALENDAR_PERIOD_6_MONTHS } from '~/profile/constants';
+
+describe('getVisibleCalendarPeriod', () => {
+ it.each`
+ width | expected
+ ${1000} | ${CALENDAR_PERIOD_12_MONTHS}
+ ${900} | ${CALENDAR_PERIOD_6_MONTHS}
+ `('returns $expected when container width is $width', ({ width, expected }) => {
+ const container = document.createElement('div');
+ jest.spyOn(container, 'getBoundingClientRect').mockReturnValueOnce({ width });
+
+ expect(getVisibleCalendarPeriod(container)).toBe(expected);
+ });
+});
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index 83302b90233..bbe04ba67f1 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -17,6 +17,10 @@ describe('GlobalSearchSidebar', () => {
resetQuery: jest.fn(),
};
+ const getterSpies = {
+ currentScope: jest.fn(() => 'issues'),
+ };
+
const createComponent = (initialState, featureFlags) => {
const store = new Vuex.Store({
state: {
@@ -24,6 +28,7 @@ describe('GlobalSearchSidebar', () => {
...initialState,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMount(GlobalSearchSidebar, {
@@ -59,6 +64,7 @@ describe('GlobalSearchSidebar', () => {
${'blobs'} | ${false} | ${true}
`('sidebar scope: $scope', ({ scope, showFilters, ShowsLanguage }) => {
beforeEach(() => {
+ getterSpies.currentScope = jest.fn(() => scope);
createComponent({ urlQuery: { scope } }, { searchBlobsLanguageAggregation: true });
});
diff --git a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
index e2a3fdeeb25..f7b35c7bb14 100644
--- a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
@@ -18,7 +18,7 @@ describe('CheckboxFilter', () => {
};
const getterSpies = {
- queryLangugageFilters: jest.fn(() => []),
+ queryLanguageFilters: jest.fn(() => []),
};
const defaultProps = {
diff --git a/spec/frontend/search/sidebar/components/language_filter_spec.js b/spec/frontend/search/sidebar/components/language_filter_spec.js
index 6870e759110..17656ba749b 100644
--- a/spec/frontend/search/sidebar/components/language_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/language_filter_spec.js
@@ -22,8 +22,8 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
};
const getterSpies = {
- langugageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
- queryLangugageFilters: jest.fn(() => []),
+ languageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ queryLanguageFilters: jest.fn(() => []),
};
const createComponent = (initialState) => {
@@ -95,7 +95,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
${'no sidebar and no query filters'} | ${false} | ${[]} | ${'true'}
`('$description', ({ sidebarDirty, queryFilters, isDisabled }) => {
beforeEach(() => {
- getterSpies.queryLangugageFilters = jest.fn(() => queryFilters);
+ getterSpies.queryLanguageFilters = jest.fn(() => queryFilters);
createComponent({ sidebarDirty, query: { ...MOCK_QUERY, language: queryFilters } });
});
@@ -156,8 +156,8 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
createComponent({});
});
- it('uses getter langugageAggregationBuckets', () => {
- expect(getterSpies.langugageAggregationBuckets).toHaveBeenCalled();
+ it('uses getter languageAggregationBuckets', () => {
+ expect(getterSpies.languageAggregationBuckets).toHaveBeenCalled();
});
it('uses action fetchLanguageAggregation', () => {
diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
index 23c158239dc..1018fae56e1 100644
--- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
@@ -14,6 +14,10 @@ describe('ScopeNavigation', () => {
fetchSidebarCount: jest.fn(),
};
+ const getterSpies = {
+ currentScope: jest.fn(() => 'issues'),
+ };
+
const createComponent = (initialState) => {
const store = new Vuex.Store({
state: {
@@ -22,6 +26,7 @@ describe('ScopeNavigation', () => {
...initialState,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMount(ScopeNavigation, {
@@ -36,9 +41,9 @@ describe('ScopeNavigation', () => {
const findNavElement = () => wrapper.find('nav');
const findGlNav = () => wrapper.findComponent(GlNav);
const findGlNavItems = () => wrapper.findAllComponents(GlNavItem);
- const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active'));
- const findGlNavItemActiveLabel = () => findGlNavItemActive().at(0).findAll('span').at(0).text();
- const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1);
+ const findGlNavItemActive = () => wrapper.find('[active=true]');
+ const findGlNavItemActiveLabel = () => findGlNavItemActive().find('[data-testid="label"]');
+ const findGlNavItemActiveCount = () => findGlNavItemActive().find('[data-testid="count"]');
describe('scope navigation', () => {
beforeEach(() => {
@@ -71,8 +76,8 @@ describe('ScopeNavigation', () => {
});
it('has correct active item', () => {
- expect(findGlNavItemActive()).toHaveLength(1);
- expect(findGlNavItemActiveLabel()).toBe('Issues');
+ expect(findGlNavItemActive().exists()).toBe(true);
+ expect(findGlNavItemActiveLabel().text()).toBe('Issues');
});
it('has correct active item count', () => {
@@ -80,7 +85,7 @@ describe('ScopeNavigation', () => {
});
it('does not have plus sign after count text', () => {
- expect(findGlNavItemActive().at(0).findComponent(GlIcon).exists()).toBe(false);
+ expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(false);
});
it('has count is highlighted correctly', () => {
@@ -90,14 +95,26 @@ describe('ScopeNavigation', () => {
describe('scope navigation sets proper state with NO url scope set', () => {
beforeEach(() => {
+ getterSpies.currentScope = jest.fn(() => 'projects');
createComponent({
urlQuery: {},
+ navigation: {
+ ...MOCK_NAVIGATION,
+ projects: {
+ ...MOCK_NAVIGATION.projects,
+ active: true,
+ },
+ issues: {
+ ...MOCK_NAVIGATION.issues,
+ active: false,
+ },
+ },
});
});
it('has correct active item', () => {
- expect(findGlNavItems().at(0).attributes('active')).toBe('true');
- expect(findGlNavItemActiveLabel()).toBe('Projects');
+ expect(findGlNavItemActive().exists()).toBe(true);
+ expect(findGlNavItemActiveLabel().text()).toBe('Projects');
});
it('has correct active item count', () => {
@@ -105,7 +122,7 @@ describe('ScopeNavigation', () => {
});
it('has correct active item count and over limit sign', () => {
- expect(findGlNavItemActive().at(0).findComponent(GlIcon).exists()).toBe(true);
+ expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/search/store/getters_spec.js b/spec/frontend/search/store/getters_spec.js
index b70a6be8d46..0ef0922c4b0 100644
--- a/spec/frontend/search/store/getters_spec.js
+++ b/spec/frontend/search/store/getters_spec.js
@@ -9,6 +9,7 @@ import {
MOCK_AGGREGATIONS,
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
TEST_FILTER_DATA,
+ MOCK_NAVIGATION,
} from '../mock_data';
describe('Global Search Store Getters', () => {
@@ -33,19 +34,26 @@ describe('Global Search Store Getters', () => {
});
});
- describe('langugageAggregationBuckets', () => {
+ describe('languageAggregationBuckets', () => {
it('returns the correct data', () => {
state.aggregations.data = MOCK_AGGREGATIONS;
- expect(getters.langugageAggregationBuckets(state)).toStrictEqual(
+ expect(getters.languageAggregationBuckets(state)).toStrictEqual(
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
);
});
});
- describe('queryLangugageFilters', () => {
+ describe('queryLanguageFilters', () => {
it('returns the correct data', () => {
state.query.language = Object.keys(TEST_FILTER_DATA.filters);
- expect(getters.queryLangugageFilters(state)).toStrictEqual(state.query.language);
+ expect(getters.queryLanguageFilters(state)).toStrictEqual(state.query.language);
+ });
+ });
+
+ describe('currentScope', () => {
+ it('returns the correct scope name', () => {
+ state.navigation = MOCK_NAVIGATION;
+ expect(getters.currentScope(state)).toBe('issues');
});
});
diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js
index 13e04ef6671..6361f8dafc4 100644
--- a/spec/frontend/work_items/components/item_title_spec.js
+++ b/spec/frontend/work_items/components/item_title_spec.js
@@ -1,8 +1,7 @@
import { shallowMount } from '@vue/test-utils';
+import { escape } from 'lodash';
import ItemTitle from '~/work_items/components/item_title.vue';
-jest.mock('lodash/escape', () => jest.fn((fn) => fn));
-
const createComponent = ({ title = 'Sample title', disabled = false } = {}) =>
shallowMount(ItemTitle, {
propsData: {
@@ -51,4 +50,25 @@ describe('ItemTitle', () => {
expect(wrapper.emitted(eventName)).toBeDefined();
});
+
+ it('renders only the text content from clipboard', async () => {
+ const htmlContent = '<strong>bold text</strong>';
+ const buildClipboardData = (data = {}) => ({
+ clipboardData: {
+ getData(mimeType) {
+ return data[mimeType];
+ },
+ types: Object.keys(data),
+ },
+ });
+
+ findInputEl().trigger(
+ 'paste',
+ buildClipboardData({
+ html: htmlContent,
+ text: htmlContent,
+ }),
+ );
+ expect(findInputEl().element.innerHTML).toBe(escape(htmlContent));
+ });
});
diff --git a/spec/graphql/mutations/release_asset_links/create_spec.rb b/spec/graphql/mutations/release_asset_links/create_spec.rb
index a5291a00799..cc6c1554866 100644
--- a/spec/graphql/mutations/release_asset_links/create_spec.rb
+++ b/spec/graphql/mutations/release_asset_links/create_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Mutations::ReleaseAssetLinks::Create do
+RSpec.describe Mutations::ReleaseAssetLinks::Create, feature_category: :release_orchestration do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private, :repository) }
diff --git a/spec/graphql/mutations/release_asset_links/delete_spec.rb b/spec/graphql/mutations/release_asset_links/delete_spec.rb
index cca7bd2ba38..3aecc44afd1 100644
--- a/spec/graphql/mutations/release_asset_links/delete_spec.rb
+++ b/spec/graphql/mutations/release_asset_links/delete_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Mutations::ReleaseAssetLinks::Delete do
+RSpec.describe Mutations::ReleaseAssetLinks::Delete, feature_category: :release_orchestration do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private, :repository) }
@@ -60,6 +60,18 @@ RSpec.describe Mutations::ReleaseAssetLinks::Delete do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
+
+ context 'when destroy process fails' do
+ before do
+ allow_next_instance_of(::Releases::Links::DestroyService) do |service|
+ allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'error'))
+ end
+ end
+
+ it 'returns errors' do
+ expect(resolve).to include(errors: 'error')
+ end
+ end
end
context 'when the current user does not have access to delete the link' do
diff --git a/spec/graphql/mutations/release_asset_links/update_spec.rb b/spec/graphql/mutations/release_asset_links/update_spec.rb
index e119cf9cc77..abb091fc68d 100644
--- a/spec/graphql/mutations/release_asset_links/update_spec.rb
+++ b/spec/graphql/mutations/release_asset_links/update_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Mutations::ReleaseAssetLinks::Update do
+RSpec.describe Mutations::ReleaseAssetLinks::Update, feature_category: :release_orchestration do
include GraphqlHelpers
let_it_be(:project) { create(:project, :private, :repository) }
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index 07ca362a450..83afeb77bad 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe UsersHelper do
include TermsHelper
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user, timezone: ActiveSupport::TimeZone::MAPPING['UTC']) }
def filter_ee_badges(badges)
badges.reject { |badge| badge[:text] == 'Is using seat' }
@@ -478,4 +478,17 @@ RSpec.describe UsersHelper do
end
end
end
+
+ describe '#user_profile_tabs_app_data' do
+ before do
+ allow(helper).to receive(:user_calendar_path).with(user, :json).and_return('/users/root/calendar.json')
+ end
+
+ it 'returns expected hash' do
+ expect(helper.user_profile_tabs_app_data(user)).to eq({
+ user_calendar_path: '/users/root/calendar.json',
+ utc_offset: 0
+ })
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/kroki_filter_spec.rb b/spec/lib/banzai/filter/kroki_filter_spec.rb
index a528c5835b2..1cd11161439 100644
--- a/spec/lib/banzai/filter/kroki_filter_spec.rb
+++ b/spec/lib/banzai/filter/kroki_filter_spec.rb
@@ -54,4 +54,11 @@ RSpec.describe Banzai::Filter::KrokiFilter, feature_category: :team_planning do
expect(doc.to_s).to start_with '<img src="http://localhost:8000/nomnoml/svg/eNqLDsgsSixJrUmtTHXOL80rsVLwzCupKUrMTNHQtC7IzMlJTE_V0KyJyVNQiE5KTSxKidXVjS5ILCrKL4lFFrSyi07LL81RyM0vLckAysRGjxo8avCowaMGjxo8avCowaMGU8lgAE7mIdc=" hidden="" class="js-render-kroki" data-diagram="nomnoml" data-diagram-src="data:text/plain;base64,W1BpcmF0ZXxleWVDb3VudDog'
end
+
+ it 'verifies diagram type to avoid possible XSS' do
+ stub_application_setting(kroki_enabled: true, kroki_url: "http://localhost:8000")
+ doc = filter(%(<a><pre lang='f/" onerror=alert(1) onload=alert(1) '><code lang="wavedrom">xss</code></pre></a>))
+
+ expect(doc.to_s).to eq %(<a><pre lang='f/" onerror=alert(1) onload=alert(1) '><code lang="wavedrom">xss</code></pre></a>)
+ end
end
diff --git a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
index 7144d23b079..f8986e8fa10 100644
--- a/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/file/remote_spec.rb
@@ -184,7 +184,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Remote, feature_category: :pi
let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
it 'includes details about blocked URL' do
- expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \
+ expect(subject).to eq "Remote file could not be fetched because URL " \
'is blocked: Requests to localhost are not allowed!'
end
end
diff --git a/spec/lib/gitlab/file_finder_spec.rb b/spec/lib/gitlab/file_finder_spec.rb
index 27750f10e87..8afaec3c381 100644
--- a/spec/lib/gitlab/file_finder_spec.rb
+++ b/spec/lib/gitlab/file_finder_spec.rb
@@ -13,124 +13,58 @@ RSpec.describe Gitlab::FileFinder, feature_category: :global_search do
let(:expected_file_by_content) { 'CHANGELOG' }
end
- context 'when code_basic_search_files_by_regexp is enabled' do
- before do
- stub_feature_flags(code_basic_search_files_by_regexp: true)
- end
-
- context 'with inclusive filters' do
- it 'filters by filename' do
- results = subject.find('files filename:wm.svg')
-
- expect(results.count).to eq(1)
- end
-
- it 'filters by path' do
- results = subject.find('white path:images')
-
- expect(results.count).to eq(2)
- end
-
- it 'filters by extension' do
- results = subject.find('files extension:md')
-
- expect(results.count).to eq(4)
- end
- end
-
- context 'with exclusive filters' do
- it 'filters by filename' do
- results = subject.find('files -filename:wm.svg')
-
- expect(results.count).to eq(26)
- end
-
- it 'filters by path' do
- results = subject.find('white -path:images')
-
- expect(results.count).to eq(5)
- end
-
- it 'filters by extension' do
- results = subject.find('files -extension:md')
+ context 'with inclusive filters' do
+ it 'filters by filename' do
+ results = subject.find('files filename:wm.svg')
- expect(results.count).to eq(23)
- end
+ expect(results.count).to eq(1)
end
- context 'with white space in the path' do
- it 'filters by path correctly' do
- results = subject.find('directory path:"with space/README.md"')
+ it 'filters by path' do
+ results = subject.find('white path:images')
- expect(results.count).to eq(1)
- end
+ expect(results.count).to eq(2)
end
- it 'does not cause N+1 query' do
- expect(Gitlab::GitalyClient).to receive(:call).at_most(10).times.and_call_original
+ it 'filters by extension' do
+ results = subject.find('files extension:md')
- subject.find(': filename:wm.svg')
+ expect(results.count).to eq(4)
end
end
- context 'when code_basic_search_files_by_regexp is disabled' do
- before do
- stub_feature_flags(code_basic_search_files_by_regexp: false)
- end
-
- context 'with inclusive filters' do
- it 'filters by filename' do
- results = subject.find('files filename:wm.svg')
-
- expect(results.count).to eq(1)
- end
-
- it 'filters by path' do
- results = subject.find('white path:images')
-
- expect(results.count).to eq(1)
- end
-
- it 'filters by extension' do
- results = subject.find('files extension:md')
+ context 'with exclusive filters' do
+ it 'filters by filename' do
+ results = subject.find('files -filename:wm.svg')
- expect(results.count).to eq(4)
- end
+ expect(results.count).to eq(26)
end
- context 'with exclusive filters' do
- it 'filters by filename' do
- results = subject.find('files -filename:wm.svg')
+ it 'filters by path' do
+ results = subject.find('white -path:images')
- expect(results.count).to eq(26)
- end
-
- it 'filters by path' do
- results = subject.find('white -path:images')
-
- expect(results.count).to eq(4)
- end
+ expect(results.count).to eq(5)
+ end
- it 'filters by extension' do
- results = subject.find('files -extension:md')
+ it 'filters by extension' do
+ results = subject.find('files -extension:md')
- expect(results.count).to eq(23)
- end
+ expect(results.count).to eq(23)
end
+ end
- context 'with white space in the path' do
- it 'filters by path correctly' do
- results = subject.find('directory path:"with space/README.md"')
+ context 'with white space in the path' do
+ it 'filters by path correctly' do
+ results = subject.find('directory path:"with space/README.md"')
- expect(results.count).to eq(1)
- end
+ expect(results.count).to eq(1)
end
+ end
- it 'does not cause N+1 query' do
- expect(Gitlab::GitalyClient).to receive(:call).at_most(10).times.and_call_original
+ it 'does not cause N+1 query' do
+ expect(Gitlab::GitalyClient).to receive(:call).at_most(10).times.and_call_original
- subject.find(': filename:wm.svg')
- end
+ subject.find(': filename:wm.svg')
end
end
end
diff --git a/spec/lib/gitlab/fogbugz_import/importer_spec.rb b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
index 9b58b772d1a..a4246809725 100644
--- a/spec/lib/gitlab/fogbugz_import/importer_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/importer_spec.rb
@@ -72,7 +72,7 @@ RSpec.describe Gitlab::FogbugzImport::Importer do
expect { subject.execute }
.to raise_error(
::Gitlab::HTTP::BlockedUrlError,
- "URL 'https://localhost:3000/api.asp' is blocked: Requests to localhost are not allowed"
+ "URL is blocked: Requests to localhost are not allowed"
)
end
end
@@ -84,7 +84,7 @@ RSpec.describe Gitlab::FogbugzImport::Importer do
expect { subject.execute }
.to raise_error(
::Gitlab::HTTP::BlockedUrlError,
- "URL 'http://192.168.0.1/api.asp' is blocked: Requests to the local network are not allowed"
+ "URL is blocked: Requests to the local network are not allowed"
)
end
end
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 5137e098e2d..dbf0252da46 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
it 'raises error' do
expect { subject }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed"
+ "URL is blocked: Requests to the local network are not allowed"
)
end
@@ -67,7 +67,7 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
it 'raises error' do
expect { subject }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed"
+ "URL is blocked: Requests to localhost are not allowed"
)
end
@@ -111,13 +111,27 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
end
end
+ context 'when http(s) environment variable is set' do
+ before do
+ stub_env('https_proxy' => 'https://my.proxy')
+ end
+
+ it 'sets up the connection' do
+ expect(connection).to be_a(Gitlab::NetHttpAdapter)
+ expect(connection.address).to eq('example.org')
+ expect(connection.hostname_override).to eq(nil)
+ expect(connection.addr_port).to eq('example.org')
+ expect(connection.port).to eq(443)
+ end
+ end
+
context 'when URL scheme is not HTTP/HTTPS' do
let(:uri) { URI('ssh://example.org') }
it 'raises error' do
expect { subject }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'ssh://example.org' is blocked: Only allowed schemes are http, https"
+ "URL is blocked: Only allowed schemes are http, https"
)
end
end
diff --git a/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb b/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb
index b1bc6b7eeaf..3d9d6e1b96b 100644
--- a/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb
+++ b/spec/lib/gitlab/import_export/remote_stream_upload_spec.rb
@@ -88,7 +88,7 @@ RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
it 'raises error' do
expect { subject.execute }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
+ "URL is blocked: Requests to localhost are not allowed"
)
end
@@ -114,7 +114,7 @@ RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
it 'raises error' do
expect { subject.execute }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
+ "URL is blocked: Requests to the local network are not allowed"
)
end
@@ -142,7 +142,7 @@ RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
expect { subject.execute }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://127.0.0.1/file.txt' is blocked: Requests to localhost are not allowed"
+ "URL is blocked: Requests to localhost are not allowed"
)
end
@@ -168,7 +168,7 @@ RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
it 'raises error' do
expect { subject.execute }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://172.16.0.0/file.txt' is blocked: Requests to the local network are not allowed"
+ "URL is blocked: Requests to the local network are not allowed"
)
end
@@ -192,7 +192,7 @@ RSpec.describe Gitlab::ImportExport::RemoteStreamUpload do
expect { subject.execute }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
- "URL 'http://example.com/file.txt' is blocked: Requests to localhost are not allowed"
+ "URL is blocked: Requests to localhost are not allowed"
)
end
end
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index f7063f2c4f2..5555990b113 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
- shared_examples 'Allowed URL' do
+ shared_examples 'Public URL' do
it 'does not raise an error' do
expect(app).to receive(:call).with(env)
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
end
end
- shared_examples 'Blocked URL' do
+ shared_examples 'Local URL' do
it 'raises an error' do
expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
end
@@ -24,24 +24,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
context 'when the URL is a public URL' do
let(:env) { { url: 'https://public-url.com' } }
- it_behaves_like 'Allowed URL'
-
- context 'with failed address check' do
- before do
- stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
- allow(Addrinfo).to receive(:getaddrinfo).and_raise(SocketError)
- end
-
- it_behaves_like 'Blocked URL'
-
- context 'with disabled dns rebinding check' do
- before do
- stub_application_setting(dns_rebinding_protection_enabled: false)
- end
-
- it_behaves_like 'Allowed URL'
- end
- end
+ it_behaves_like 'Public URL'
end
context 'when the URL is a localhost address' do
@@ -52,7 +35,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
end
- it_behaves_like 'Blocked URL'
+ it_behaves_like 'Local URL'
end
context 'when localhost requests are allowed' do
@@ -60,7 +43,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
- it_behaves_like 'Allowed URL'
+ it_behaves_like 'Public URL'
end
end
@@ -72,7 +55,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
end
- it_behaves_like 'Blocked URL'
+ it_behaves_like 'Local URL'
end
context 'when local network requests are allowed' do
@@ -80,7 +63,7 @@ RSpec.describe Gitlab::Octokit::Middleware, feature_category: :importers do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
- it_behaves_like 'Allowed URL'
+ it_behaves_like 'Public URL'
end
end
diff --git a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
index e3706a4b106..f09fa3548f8 100644
--- a/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
@@ -43,10 +43,7 @@ RSpec.describe Gitlab::Prometheus::Queries::ValidateQuery do
context 'Gitlab::HTTP::BlockedUrlError' do
let(:api_url) { 'http://192.168.1.1' }
- let(:message) do
- "URL 'http://192.168.1.1/api/v1/query?query=avg%28metric%29&time=#{Time.now.to_f}'" \
- " is blocked: Requests to the local network are not allowed"
- end
+ let(:message) { "URL is blocked: Requests to the local network are not allowed" }
before do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 0d037984799..05f7af7606d 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -174,17 +174,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
expect { subject }.to raise_error(described_class::BlockedUrlError)
end
-
- context 'with HTTP_PROXY' do
- before do
- allow(Gitlab).to receive(:http_proxy_env?).and_return(true)
- end
-
- it_behaves_like 'validates URI and hostname' do
- let(:expected_uri) { import_url }
- let(:expected_hostname) { nil }
- end
- end
end
context 'when domain is too long' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6836d1b51ef..abd29a12b47 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1922,62 +1922,6 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
- describe '#failed_but_allowed?' do
- subject { build.failed_but_allowed? }
-
- context 'when build is not allowed to fail' do
- before do
- build.allow_failure = false
- end
-
- context 'and build.status is success' do
- before do
- build.status = 'success'
- end
-
- it { is_expected.to be_falsey }
- end
-
- context 'and build.status is failed' do
- before do
- build.status = 'failed'
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when build is allowed to fail' do
- before do
- build.allow_failure = true
- end
-
- context 'and build.status is success' do
- before do
- build.status = 'success'
- end
-
- it { is_expected.to be_falsey }
- end
-
- context 'and build status is failed' do
- before do
- build.status = 'failed'
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when build is a manual action' do
- before do
- build.status = 'manual'
- end
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
describe 'flags' do
describe '#cancelable?' do
subject { build }
@@ -5198,52 +5142,42 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
subject { build.debug_mode? }
context 'when CI_DEBUG_TRACE=true is in variables' do
- context 'when in instance variables' do
- before do
- create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true')
+ ['true', 1, 'y'].each do |value|
+ it 'reflects instance variables' do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: value)
+
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects group variables' do
+ create(:ci_group_variable, key: 'CI_DEBUG_TRACE', value: value, group: project.group)
- context 'when in group variables' do
- before do
- create(:ci_group_variable, key: 'CI_DEBUG_TRACE', value: 'true', group: project.group)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects pipeline variables' do
+ create(:ci_pipeline_variable, key: 'CI_DEBUG_TRACE', value: value, pipeline: pipeline)
- context 'when in pipeline variables' do
- before do
- create(:ci_pipeline_variable, key: 'CI_DEBUG_TRACE', value: 'true', pipeline: pipeline)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects project variables' do
+ create(:ci_variable, key: 'CI_DEBUG_TRACE', value: value, project: project)
- context 'when in project variables' do
- before do
- create(:ci_variable, key: 'CI_DEBUG_TRACE', value: 'true', project: project)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects job variables' do
+ create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: value, job: build)
- context 'when in job variables' do
- before do
- create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: build)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'when in yaml variables' do
+ build.update!(yaml_variables: [{ key: :CI_DEBUG_TRACE, value: value.to_s }])
- context 'when in yaml variables' do
- before do
- build.update!(yaml_variables: [{ key: :CI_DEBUG_TRACE, value: 'true' }])
+ is_expected.to eq true
end
-
- it { is_expected.to eq true }
end
end
@@ -5252,58 +5186,64 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
context 'when CI_DEBUG_SERVICES=true is in variables' do
- context 'when in instance variables' do
- before do
- create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: 'true')
+ ['true', 1, 'y'].each do |value|
+ it 'reflects instance variables' do
+ create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: value)
+
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects group variables' do
+ create(:ci_group_variable, key: 'CI_DEBUG_SERVICES', value: value, group: project.group)
- context 'when in group variables' do
- before do
- create(:ci_group_variable, key: 'CI_DEBUG_SERVICES', value: 'true', group: project.group)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects pipeline variables' do
+ create(:ci_pipeline_variable, key: 'CI_DEBUG_SERVICES', value: value, pipeline: pipeline)
- context 'when in pipeline variables' do
- before do
- create(:ci_pipeline_variable, key: 'CI_DEBUG_SERVICES', value: 'true', pipeline: pipeline)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects project variables' do
+ create(:ci_variable, key: 'CI_DEBUG_SERVICES', value: value, project: project)
- context 'when in project variables' do
- before do
- create(:ci_variable, key: 'CI_DEBUG_SERVICES', value: 'true', project: project)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'reflects job variables' do
+ create(:ci_job_variable, key: 'CI_DEBUG_SERVICES', value: value, job: build)
- context 'when in job variables' do
- before do
- create(:ci_job_variable, key: 'CI_DEBUG_SERVICES', value: 'true', job: build)
+ is_expected.to eq true
end
- it { is_expected.to eq true }
- end
+ it 'when in yaml variables' do
+ build.update!(yaml_variables: [{ key: :CI_DEBUG_SERVICES, value: value.to_s }])
- context 'when in yaml variables' do
- before do
- build.update!(yaml_variables: [{ key: :CI_DEBUG_SERVICES, value: 'true' }])
+ is_expected.to eq true
end
-
- it { is_expected.to eq true }
end
end
context 'when CI_DEBUG_SERVICES is not in variables' do
it { is_expected.to eq false }
end
+
+ context 'when metadata has debug_trace_enabled true' do
+ before do
+ build.metadata.update!(debug_trace_enabled: true)
+ end
+
+ it { is_expected.to eq true }
+ end
+
+ context 'when metadata has debug_trace_enabled false' do
+ before do
+ build.metadata.update!(debug_trace_enabled: false)
+ end
+
+ it { is_expected.to eq false }
+ end
end
describe '#drop_with_exit_code!' do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 4ff451af9de..da87951d9ef 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -422,29 +422,6 @@ RSpec.describe CommitStatus do
end
end
- describe '.exclude_ignored' do
- subject { described_class.exclude_ignored.order(:id) }
-
- let(:statuses) do
- [create_status(when: 'manual', status: 'skipped'),
- create_status(when: 'manual', status: 'success'),
- create_status(when: 'manual', status: 'failed'),
- create_status(when: 'on_failure', status: 'skipped'),
- create_status(when: 'on_failure', status: 'success'),
- create_status(when: 'on_failure', status: 'failed'),
- create_status(allow_failure: true, status: 'success'),
- create_status(allow_failure: true, status: 'failed'),
- create_status(allow_failure: false, status: 'success'),
- create_status(allow_failure: false, status: 'failed'),
- create_status(allow_failure: true, status: 'manual'),
- create_status(allow_failure: false, status: 'manual')]
- end
-
- it 'returns statuses without what we want to ignore' do
- is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9, 11))
- end
- end
-
describe '.failed_but_allowed' do
subject { described_class.failed_but_allowed.order(:id) }
diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb
index 7af96c7025a..a247881899f 100644
--- a/spec/models/integration_spec.rb
+++ b/spec/models/integration_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Integration do
+RSpec.describe Integration, feature_category: :integrations do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { create(:group) }
@@ -854,6 +854,7 @@ RSpec.describe Integration do
{ name: 'api_key', type: 'password' },
{ name: 'password', type: 'password' },
{ name: 'password_field', type: 'password' },
+ { name: 'webhook' },
{ name: 'some_safe_field' },
{ name: 'safe_field' },
{ name: 'url' },
@@ -881,6 +882,7 @@ RSpec.describe Integration do
field :api_key, type: 'password'
field :password, type: 'password'
field :password_field, type: 'password'
+ field :webhook
field :some_safe_field
field :safe_field
field :url
@@ -1092,6 +1094,8 @@ RSpec.describe Integration do
field :bar, type: 'password'
field :password
+ field :webhook
+
field :with_help, help: -> { 'help' }
field :select, type: 'select'
field :boolean, type: 'checkbox'
@@ -1142,7 +1146,7 @@ RSpec.describe Integration do
it 'registers fields in the fields list' do
expect(integration.fields.pluck(:name)).to match_array %w[
- foo foo_p foo_dt bar password with_help select boolean
+ foo foo_p foo_dt bar password with_help select boolean webhook
]
expect(integration.api_field_names).to match_array %w[
@@ -1157,6 +1161,7 @@ RSpec.describe Integration do
have_attributes(name: 'foo_dt', type: 'text'),
have_attributes(name: 'bar', type: 'password'),
have_attributes(name: 'password', type: 'password'),
+ have_attributes(name: 'webhook', type: 'text'),
have_attributes(name: 'with_help', help: 'help'),
have_attributes(name: 'select', type: 'select'),
have_attributes(name: 'boolean', type: 'checkbox')
diff --git a/spec/models/integrations/datadog_spec.rb b/spec/models/integrations/datadog_spec.rb
index 65ecd9bee83..2d1e23b103f 100644
--- a/spec/models/integrations/datadog_spec.rb
+++ b/spec/models/integrations/datadog_spec.rb
@@ -3,7 +3,7 @@ require 'securerandom'
require 'spec_helper'
-RSpec.describe Integrations::Datadog do
+RSpec.describe Integrations::Datadog, feature_category: :integrations do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/models/integrations/prometheus_spec.rb b/spec/models/integrations/prometheus_spec.rb
index 3c3850854b3..aa248abd3bb 100644
--- a/spec/models/integrations/prometheus_spec.rb
+++ b/spec/models/integrations/prometheus_spec.rb
@@ -239,6 +239,7 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
context 'behind IAP' do
let(:manual_configuration) { true }
+ let(:google_iap_service_account_json) { Gitlab::Json.generate(google_iap_service_account) }
let(:google_iap_service_account) do
{
@@ -259,7 +260,7 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
end
def stub_iap_request
- integration.google_iap_service_account_json = Gitlab::Json.generate(google_iap_service_account)
+ integration.google_iap_service_account_json = google_iap_service_account_json
integration.google_iap_audience_client_id = 'IAP_CLIENT_ID.apps.googleusercontent.com'
stub_request(:post, 'https://oauth2.googleapis.com/token')
@@ -278,6 +279,17 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
expect(integration.prometheus_client.send(:options)[:headers]).to eq(authorization: "Bearer FOO")
end
+ context 'with invalid IAP JSON' do
+ let(:google_iap_service_account_json) { 'invalid json' }
+
+ it 'does not include authorization header' do
+ stub_iap_request
+
+ expect(integration.prometheus_client).not_to be_nil
+ expect(integration.prometheus_client.send(:options)).not_to have_key(:headers)
+ end
+ end
+
context 'when passed with token_credential_uri', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/284819' do
let(:malicious_host) { 'http://example.com' }
@@ -477,4 +489,45 @@ RSpec.describe Integrations::Prometheus, :use_clean_rails_memory_store_caching,
end
end
end
+
+ describe '#google_iap_service_account_json' do
+ subject(:iap_details) { integration.google_iap_service_account_json }
+
+ before do
+ integration.google_iap_service_account_json = value
+ end
+
+ context 'with valid JSON' do
+ let(:masked_value) { described_class::MASKED_VALUE }
+ let(:json) { Gitlab::Json.parse(iap_details) }
+
+ let(:value) do
+ Gitlab::Json.generate({
+ type: 'service_account',
+ private_key: 'SECRET',
+ foo: 'secret',
+ nested: {
+ key: 'value'
+ }
+ })
+ end
+
+ it 'masks all JSON values', issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/384580' do
+ expect(json).to eq(
+ 'type' => masked_value,
+ 'private_key' => masked_value,
+ 'foo' => masked_value,
+ 'nested' => masked_value
+ )
+ end
+ end
+
+ context 'with invalid JSON' do
+ where(:value) { [nil, '', ' ', 'invalid json'] }
+
+ with_them do
+ it { is_expected.to eq(value) }
+ end
+ end
+ end
end
diff --git a/spec/requests/api/ci/jobs_spec.rb b/spec/requests/api/ci/jobs_spec.rb
index 10dd9c3b556..8b3ec59b785 100644
--- a/spec/requests/api/ci/jobs_spec.rb
+++ b/spec/requests/api/ci/jobs_spec.rb
@@ -750,11 +750,7 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
end
- context 'when ci_debug_trace is set to true' do
- before_all do
- create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: true)
- end
-
+ shared_examples_for "additional access criteria" do
where(:public_builds, :user_project_role, :expected_status) do
true | 'developer' | :ok
true | 'guest' | :forbidden
@@ -776,30 +772,28 @@ RSpec.describe API::Ci::Jobs, feature_category: :continuous_integration do
end
end
- context 'when ci_debug_services is set to true' do
- before_all do
- create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: true)
+ describe 'when metadata debug_trace_enabled is set to true' do
+ before do
+ job.metadata.update!(debug_trace_enabled: true)
end
- where(:public_builds, :user_project_role, :expected_status) do
- true | 'developer' | :ok
- true | 'guest' | :forbidden
- false | 'developer' | :ok
- false | 'guest' | :forbidden
- end
+ it_behaves_like "additional access criteria"
+ end
- with_them do
- before do
- project.update!(public_builds: public_builds)
- project.add_role(user, user_project_role)
+ context 'when ci_debug_trace is set to true' do
+ before_all do
+ create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: true)
+ end
- get api("/projects/#{project.id}/jobs/#{job.id}/trace", api_user)
- end
+ it_behaves_like "additional access criteria"
+ end
- it 'renders successfully to authorized users' do
- expect(response).to have_gitlab_http_status(expected_status)
- end
+ context 'when ci_debug_services is set to true' do
+ before_all do
+ create(:ci_instance_variable, key: 'CI_DEBUG_SERVICES', value: true)
end
+
+ it_behaves_like "additional access criteria"
end
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e474070f621..57050c39083 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -249,6 +249,18 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
end
end
+ context 'when per_page is over 100' do
+ let(:per_page) { 101 }
+
+ it 'returns 100 commits (maximum)' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(
+ hash_including(ref: ref_name, limit: 100, offset: 0)
+ )
+
+ request
+ end
+ end
+
context 'when pagination params are invalid' do
let_it_be(:project) { create(:project, :repository) }
@@ -279,7 +291,7 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
where(:page, :per_page, :error_message, :status) do
0 | nil | nil | :success
- -10 | nil | nil | :internal_server_error
+ -10 | nil | nil | :success
'a' | nil | 'page is invalid' | :bad_request
nil | 0 | 'per_page has a value not allowed' | :bad_request
nil | -1 | nil | :success
@@ -297,6 +309,18 @@ RSpec.describe API::Commits, feature_category: :source_code_management do
end
end
end
+
+ context 'when per_page is below 0' do
+ let(:per_page) { -100 }
+
+ it 'returns 20 commits (default)' do
+ expect(Gitlab::Git::Commit).to receive(:where).with(
+ hash_including(ref: ref_name, limit: 20, offset: 0)
+ )
+
+ request
+ end
+ end
end
end
end
diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb
index 462cc1e3b5d..4b388304621 100644
--- a/spec/requests/api/release/links_spec.rb
+++ b/spec/requests/api/release/links_spec.rb
@@ -377,6 +377,15 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do
expect(response).to match_response_schema('release/link')
end
+ context 'when params are invalid' do
+ it 'returns 400 error' do
+ put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
+ params: params.merge(url: 'wrong_url')
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
context 'when using `direct_asset_path`' do
it 'updates the release link' do
put api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer),
@@ -534,6 +543,21 @@ RSpec.describe API::Release::Links, feature_category: :release_orchestration do
end
end
+ context 'when destroy process fails' do
+ before do
+ allow_next_instance_of(::Releases::Links::DestroyService) do |service|
+ allow(service).to receive(:execute).and_return(ServiceResponse.error(message: 'error'))
+ end
+ end
+
+ it_behaves_like '400 response' do
+ let(:message) { 'error' }
+ let(:request) do
+ delete api("/projects/#{project.id}/releases/v0.1/assets/links/#{release_link.id}", maintainer)
+ end
+ end
+ end
+
context 'when there are no corresponding release link' do
let!(:release_link) {}
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index b02c7135b7b..ab5e04246e8 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -111,6 +111,22 @@ RSpec.describe API::Tags, feature_category: :source_code_management do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'repository tags'
+
+ context 'and releases are private' do
+ before do
+ create(:release, project: project, tag: tag_name)
+ project.project_feature.update!(releases_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns the repository tags without release information' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/tags')
+ expect(response).to include_pagination_headers
+ expect(json_response.map { |r| r.has_key?('release') }).to all(be_falsey)
+ end
+ end
end
context 'when unauthenticated', 'and project is private' do
@@ -251,6 +267,21 @@ RSpec.describe API::Tags, feature_category: :source_code_management do
it_behaves_like "cache expired"
end
+
+ context 'when user is not allowed to :read_release' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ project.project_feature.update!(releases_access_level: ProjectFeature::PRIVATE)
+
+ get api(route, user) # Cache as a user allowed to :read_release
+ end
+
+ it "isn't cached" do
+ expect(API::Entities::Tag).to receive(:represent).exactly(3).times
+
+ get api(route, nil)
+ end
+ end
end
context 'when gitaly is unavailable' do
@@ -302,6 +333,21 @@ RSpec.describe API::Tags, feature_category: :source_code_management do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'repository tag'
+
+ context 'and releases are private' do
+ before do
+ create(:release, project: project, tag: tag_name)
+ project.project_feature.update!(releases_access_level: ProjectFeature::PRIVATE)
+ end
+
+ it 'returns the repository tags without release information' do
+ get api(route, current_user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/tag')
+ expect(json_response.has_key?('release')).to be_falsey
+ end
+ end
end
context 'when unauthenticated', 'and project is private' do
@@ -328,6 +374,24 @@ RSpec.describe API::Tags, feature_category: :source_code_management do
let(:request) { get api(route, guest) }
end
end
+
+ context 'with releases' do
+ let(:description) { 'Awesome release!' }
+
+ before do
+ create(:release, project: project, tag: tag_name, description: description)
+ end
+
+ it 'returns release information' do
+ get api(route, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/tag')
+
+ expect(json_response['message']).to eq(tag_message)
+ expect(json_response.dig('release', 'description')).to eq(description)
+ end
+ end
end
describe 'POST /projects/:id/repository/tags' do
diff --git a/spec/services/releases/links/create_service_spec.rb b/spec/services/releases/links/create_service_spec.rb
new file mode 100644
index 00000000000..aa154647509
--- /dev/null
+++ b/spec/services/releases/links/create_service_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::Links::CreateService, feature_category: :release_orchestration do
+ let(:service) { described_class.new(release, user, params) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') }
+
+ let(:params) { { name: name, url: url, direct_asset_path: direct_asset_path, link_type: link_type } }
+ let(:name) { 'link' }
+ let(:url) { 'https://example.com' }
+ let(:direct_asset_path) { '/path' }
+ let(:link_type) { 'other' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject(:execute) { service.execute }
+
+ let(:link) { subject.payload[:link] }
+
+ it 'successfully creates a release link' do
+ expect { execute }.to change { Releases::Link.count }.by(1)
+
+ expect(link).to have_attributes(
+ name: name,
+ url: url,
+ filepath: direct_asset_path,
+ link_type: link_type
+ )
+ end
+
+ context 'when user does not have access to create release link' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns an error' do
+ expect { execute }.not_to change { Releases::Link.count }
+
+ is_expected.to be_error
+ expect(execute.message).to include('Access Denied')
+ end
+ end
+
+ context 'when url is invalid' do
+ let(:url) { 'not_a_url' }
+
+ it 'returns an error' do
+ expect { execute }.not_to change { Releases::Link.count }
+
+ is_expected.to be_error
+ expect(execute.message[0]).to include('Url is blocked')
+ end
+ end
+
+ context 'when both direct_asset_path and filepath are provided' do
+ let(:params) { super().merge(filepath: '/filepath') }
+
+ it 'prefers direct_asset_path' do
+ is_expected.to be_success
+
+ expect(link.filepath).to eq(direct_asset_path)
+ end
+ end
+
+ context 'when only filepath is set' do
+ let(:params) { super().merge(filepath: '/filepath') }
+ let(:direct_asset_path) { nil }
+
+ it 'uses filepath' do
+ is_expected.to be_success
+
+ expect(link.filepath).to eq('/filepath')
+ end
+ end
+ end
+end
diff --git a/spec/services/releases/links/destroy_service_spec.rb b/spec/services/releases/links/destroy_service_spec.rb
new file mode 100644
index 00000000000..fed98a62aa7
--- /dev/null
+++ b/spec/services/releases/links/destroy_service_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::Links::DestroyService, feature_category: :release_orchestration do
+ let(:service) { described_class.new(release, user, {}) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') }
+
+ let!(:release_link) do
+ create(
+ :release_link,
+ release: release,
+ name: 'awesome-app.dmg',
+ url: 'https://example.com/download/awesome-app.dmg'
+ )
+ end
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject(:execute) { service.execute(release_link) }
+
+ it 'successfully deletes a release link' do
+ expect { execute }.to change { release.links.count }.by(-1)
+
+ is_expected.to be_success
+ end
+
+ context 'when user does not have access to delete release link' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns an error' do
+ expect { execute }.not_to change { release.links.count }
+
+ is_expected.to be_error
+ expect(execute.message).to include('Access Denied')
+ end
+ end
+
+ context 'when release link does not exist' do
+ let(:release_link) { nil }
+
+ it 'returns an error' do
+ expect { execute }.not_to change { release.links.count }
+
+ is_expected.to be_error
+ expect(execute.message).to eq('Link does not exist')
+ end
+ end
+
+ context 'when release link deletion failed' do
+ before do
+ allow(release_link).to receive(:destroy).and_return(false)
+ end
+
+ it 'returns an error' do
+ expect { execute }.not_to change { release.links.count }
+
+ is_expected.to be_error
+ end
+ end
+ end
+end
diff --git a/spec/services/releases/links/update_service_spec.rb b/spec/services/releases/links/update_service_spec.rb
new file mode 100644
index 00000000000..40756c7eced
--- /dev/null
+++ b/spec/services/releases/links/update_service_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Releases::Links::UpdateService, feature_category: :release_orchestration do
+ let(:service) { described_class.new(release, user, params) }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:release) { create(:release, project: project, author: user, tag: 'v1.1.0') }
+
+ let(:release_link) do
+ create(
+ :release_link,
+ release: release,
+ name: 'awesome-app.dmg',
+ url: 'https://example.com/download/awesome-app.dmg'
+ )
+ end
+
+ let(:params) { { name: name, url: url, direct_asset_path: direct_asset_path, link_type: link_type } }
+ let(:name) { 'link' }
+ let(:url) { 'https://example.com' }
+ let(:direct_asset_path) { '/path' }
+ let(:link_type) { 'other' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ describe '#execute' do
+ subject(:execute) { service.execute(release_link) }
+
+ let(:updated_link) { execute.payload[:link] }
+
+ it 'successfully updates a release link' do
+ is_expected.to be_success
+
+ expect(updated_link).to have_attributes(
+ name: name,
+ url: url,
+ filepath: direct_asset_path,
+ link_type: link_type
+ )
+ end
+
+ context 'when user does not have access to update release link' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns an error' do
+ is_expected.to be_error
+ expect(execute.message).to include('Access Denied')
+ end
+ end
+
+ context 'when url is invalid' do
+ let(:url) { 'not_a_url' }
+
+ it 'returns an error' do
+ is_expected.to be_error
+ expect(execute.message[0]).to include('Url is blocked')
+ end
+ end
+
+ context 'when both direct_asset_path and filepath are provided' do
+ let(:params) { super().merge(filepath: '/filepath') }
+
+ it 'prefers direct_asset_path' do
+ is_expected.to be_success
+
+ expect(updated_link.filepath).to eq(direct_asset_path)
+ end
+ end
+
+ context 'when only filepath is set' do
+ let(:params) { super().merge(filepath: '/filepath') }
+ let(:direct_asset_path) { nil }
+
+ it 'uses filepath' do
+ is_expected.to be_success
+
+ expect(updated_link.filepath).to eq('/filepath')
+ end
+ end
+ end
+end
diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb
index f0eae9554dc..d81bbc30318 100644
--- a/spec/services/resource_access_tokens/create_service_spec.rb
+++ b/spec/services/resource_access_tokens/create_service_spec.rb
@@ -27,6 +27,13 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
end
+ shared_examples 'correct error message' do
+ it 'returns correct error message' do
+ expect(subject.error?).to be true
+ expect(subject.errors).to include(error_message)
+ end
+ end
+
shared_examples 'allows creation of bot with valid params' do
it { expect { subject }.to change { User.count }.by(1) }
@@ -215,16 +222,11 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
context 'when invalid scope is passed' do
+ let(:error_message) { 'Scopes can only contain available scopes' }
let_it_be(:params) { { scopes: [:invalid_scope] } }
it_behaves_like 'token creation fails'
-
- it 'returns the scope error message' do
- response = subject
-
- expect(response.error?).to be true
- expect(response.errors).to include("Scopes can only contain available scopes")
- end
+ it_behaves_like 'correct error message'
end
end
@@ -232,6 +234,7 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
let_it_be(:bot_user) { create(:user, :project_bot) }
let(:unpersisted_member) { build(:project_member, source: resource, user: bot_user) }
+ let(:error_message) { 'Could not provision maintainer access to project access token' }
before do
allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
@@ -241,13 +244,7 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
it_behaves_like 'token creation fails'
-
- it 'returns the provisioning error message' do
- response = subject
-
- expect(response.error?).to be true
- expect(response.errors).to include("Could not provision maintainer access to project access token")
- end
+ it_behaves_like 'correct error message'
end
end
@@ -261,14 +258,10 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
end
shared_examples 'when user does not have permission to create a resource bot' do
- it_behaves_like 'token creation fails'
-
- it 'returns the permission error message' do
- response = subject
+ let(:error_message) { "User does not have permission to create #{resource_type} access token" }
- expect(response.error?).to be true
- expect(response.errors).to include("User does not have permission to create #{resource_type} access token")
- end
+ it_behaves_like 'token creation fails'
+ it_behaves_like 'correct error message'
end
context 'when resource is a project' do
@@ -288,11 +281,19 @@ RSpec.describe ResourceAccessTokens::CreateService, feature_category: :system_ac
let_it_be(:params) { { access_level: Gitlab::Access::OWNER } }
context 'when the executor is a MAINTAINER' do
- it 'does not add the bot user with the specified access level in the resource' do
- response = subject
+ let(:error_message) { 'Could not provision owner access to project access token' }
- expect(response.error?).to be true
- expect(response.errors).to include('Could not provision owner access to project access token')
+ context 'with OWNER access_level, in integer format' do
+ it_behaves_like 'token creation fails'
+ it_behaves_like 'correct error message'
+ end
+
+ context 'with OWNER access_level, in string format' do
+ let(:error_message) { 'Could not provision owner access to project access token' }
+ let_it_be(:params) { { access_level: Gitlab::Access::OWNER.to_s } }
+
+ it_behaves_like 'token creation fails'
+ it_behaves_like 'correct error message'
end
end
diff --git a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
index f07e4f6c9c3..9612b657093 100644
--- a/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
+++ b/spec/support/shared_contexts/features/integrations/integrations_shared_context.rb
@@ -99,6 +99,7 @@ Integration.available_integration_names.each do |integration|
def initialize_integration(integration, attrs = {})
record = project.find_or_initialize_integration(integration)
+ record.reset_updated_properties if integration == 'datadog'
record.attributes = attrs
record.properties = integration_attrs
record.save!
diff --git a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
index 553e9f10b0d..cef76bd4356 100644
--- a/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
+++ b/spec/support/shared_examples/initializers/uses_gitlab_url_blocker_shared_examples.rb
@@ -33,7 +33,7 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do
expect { make_request('https://example.com') }
.to raise_error(url_blocked_error_class,
- "URL 'https://example.com' is blocked: Requests to the local network are not allowed")
+ "URL is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request that resolves to a localhost address' do
@@ -41,19 +41,19 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do
expect { make_request('https://example.com') }
.to raise_error(url_blocked_error_class,
- "URL 'https://example.com' is blocked: Requests to localhost are not allowed")
+ "URL is blocked: Requests to localhost are not allowed")
end
it 'raises error when it is a request to local address' do
expect { make_request('http://172.16.0.0') }
.to raise_error(url_blocked_error_class,
- "URL 'http://172.16.0.0' is blocked: Requests to the local network are not allowed")
+ "URL is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request to localhost address' do
expect { make_request('http://127.0.0.1') }
.to raise_error(url_blocked_error_class,
- "URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
+ "URL is blocked: Requests to localhost are not allowed")
end
end
@@ -69,13 +69,13 @@ RSpec.shared_examples 'a request using Gitlab::UrlBlocker' do
it 'raises error when it is a request to local address' do
expect { make_request('https://172.16.0.0:8080') }
.to raise_error(url_blocked_error_class,
- "URL 'https://172.16.0.0:8080' is blocked: Requests to the local network are not allowed")
+ "URL is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request to localhost address' do
expect { make_request('https://127.0.0.1:8080') }
.to raise_error(url_blocked_error_class,
- "URL 'https://127.0.0.1:8080' is blocked: Requests to localhost are not allowed")
+ "URL is blocked: Requests to localhost are not allowed")
end
end
diff --git a/tooling/lib/tooling/find_codeowners.rb b/tooling/lib/tooling/find_codeowners.rb
index cc37d4db1ec..e542ab9967c 100644
--- a/tooling/lib/tooling/find_codeowners.rb
+++ b/tooling/lib/tooling/find_codeowners.rb
@@ -48,11 +48,7 @@ module Tooling
def load_config
config_path = "#{__dir__}/../../config/CODEOWNERS.yml"
- if YAML.respond_to?(:safe_load_file) # Ruby 3.0+
- YAML.safe_load_file(config_path, symbolize_names: true)
- else
- YAML.safe_load(File.read(config_path), symbolize_names: true)
- end
+ YAML.safe_load_file(config_path, symbolize_names: true)
end
# Copied and modified from ee/lib/gitlab/code_owners/file.rb