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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-11-08 18:09:34 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-11-08 18:09:34 +0300
commitb5bdf6e5219b3b57107aee49ba7c103affb65dd9 (patch)
tree54c1ea8b3140d60af9a6c64867edc0a484ef7735
parent81f062b841f6062601662061850934a51e77ceea (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml38
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml9
-rw-r--r--.rubocop_todo/gitlab/json.yml1
-rw-r--r--.rubocop_todo/gitlab/no_code_coverage_comment.yml1
-rw-r--r--.rubocop_todo/gitlab/rspec/avoid_setup.yml3
-rw-r--r--.rubocop_todo/gitlab/service_response.yml1
-rw-r--r--.rubocop_todo/layout/first_array_element_indentation.yml1
-rw-r--r--.rubocop_todo/layout/trailing_whitespace.yml1
-rw-r--r--.rubocop_todo/rails/active_record_callbacks_order.yml1
-rw-r--r--.rubocop_todo/rails/file_path.yml1
-rw-r--r--.rubocop_todo/rails/helper_instance_variable.yml1
-rw-r--r--.rubocop_todo/rails/index_with.yml1
-rw-r--r--.rubocop_todo/rails/inverse_of.yml1
-rw-r--r--.rubocop_todo/rails/redundant_foreign_key.yml1
-rw-r--r--.rubocop_todo/rspec/factory_bot/avoid_create.yml1
-rw-r--r--.rubocop_todo/rspec/file_path.yml1
-rw-r--r--.rubocop_todo/rspec/multiple_memoized_helpers.yml1
-rw-r--r--.rubocop_todo/rspec/scattered_let.yml1
-rw-r--r--.rubocop_todo/style/accessor_grouping.yml1
-rw-r--r--.rubocop_todo/style/bare_percent_literals.yml1
-rw-r--r--.rubocop_todo/style/empty_method.yml1
-rw-r--r--.rubocop_todo/style/explicit_block_argument.yml1
-rw-r--r--.rubocop_todo/style/keyword_parameters_order.yml1
-rw-r--r--.rubocop_todo/style/numeric_literal_prefix.yml1
-rw-r--r--.rubocop_todo/style/redundant_begin.yml1
-rw-r--r--.rubocop_todo/style/single_argument_dig.yml1
-rw-r--r--.rubocop_todo/style/sole_nested_conditional.yml1
-rw-r--r--.rubocop_todo/style/special_global_vars.yml4
-rw-r--r--.rubocop_todo/style/string_literals_in_interpolation.yml1
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue5
-rw-r--r--app/assets/javascripts/boards/components/board_card_move_to_position.vue15
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue13
-rw-r--r--app/assets/javascripts/search/sidebar/components/app.vue47
-rw-r--r--app/assets/javascripts/search/sidebar/components/results_filters.vue49
-rw-r--r--app/assets/javascripts/search/sidebar/components/scope_navigation.vue66
-rw-r--r--app/assets/javascripts/search/sidebar/constants/index.js11
-rw-r--r--app/assets/javascripts/search/store/actions.js18
-rw-r--r--app/assets/javascripts/search/store/index.js4
-rw-r--r--app/assets/javascripts/search/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/search/store/mutations.js4
-rw-r--r--app/assets/javascripts/search/store/state.js3
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss10
-rw-r--r--app/controllers/groups/settings/repository_controller.rb3
-rw-r--r--app/controllers/projects/artifacts_controller.rb6
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb1
-rw-r--r--app/controllers/projects/settings/repository_controller.rb3
-rw-r--r--app/helpers/search_helper.rb31
-rw-r--r--app/models/ci/bridge.rb6
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/shared/deploy_tokens/_form.html.haml2
-rw-r--r--app/views/shared/deploy_tokens/_index.html.haml21
-rw-r--r--config/feature_flags/development/ai_assist_api.yml (renamed from config/feature_flags/development/ci_recreate_downstream_pipeline.yml)10
-rw-r--r--config/feature_flags/development/ai_assist_flag.yml8
-rw-r--r--config/feature_flags/development/ajax_new_deploy_token.yml8
-rw-r--r--config/metrics/counts_28d/20220621085114_unique_active_users_monthly.yml2
-rw-r--r--config/metrics/counts_all/20210216183000_package_events_i_package_pull_package_by_guest.yml2
-rw-r--r--config/metrics/counts_all/20210216183005_package_events_i_package_push_package_by_deploy_token.yml2
-rw-r--r--config/open_api.yml4
-rw-r--r--doc/.vale/vale-json.tmpl65
-rw-r--r--doc/development/contributing/merge_request_workflow.md4
-rw-r--r--doc/user/group/epics/manage_epics.md32
-rw-r--r--doc/user/permissions.md2
-rw-r--r--lib/api/api.rb8
-rw-r--r--lib/api/commits.rb200
-rw-r--r--lib/api/entities/basic_ref.rb3
-rw-r--r--lib/api/entities/ci/lint/result.rb17
-rw-r--r--lib/api/entities/commit.rb2
-rw-r--r--lib/api/entities/commit_note.rb20
-rw-r--r--lib/api/entities/commit_signature.rb4
-rw-r--r--lib/api/entities/compare.rb13
-rw-r--r--lib/api/entities/contributor.rb6
-rw-r--r--lib/api/entities/diff.rb16
-rw-r--r--lib/api/entities/tree_object.rb7
-rw-r--r--lib/api/entities/user_public.rb2
-rw-r--r--lib/api/entities/x509_certificate.rb17
-rw-r--r--lib/api/entities/x509_issuer.rb11
-rw-r--r--lib/api/entities/x509_signature.rb2
-rw-r--r--lib/api/group_export.rb45
-rw-r--r--lib/api/lint.rb41
-rw-r--r--lib/api/repositories.rb72
-rw-r--r--spec/controllers/groups/settings/repository_controller_spec.rb117
-rw-r--r--spec/controllers/projects/artifacts_controller_spec.rb34
-rw-r--r--spec/controllers/projects/settings/repository_controller_spec.rb102
-rw-r--r--spec/features/groups/settings/repository_spec.rb23
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb1
-rw-r--r--spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb1
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js11
-rw-r--r--spec/frontend/boards/board_list_spec.js6
-rw-r--r--spec/frontend/boards/components/board_card_move_to_position_spec.js1
-rw-r--r--spec/frontend/search/mock_data.js84
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js109
-rw-r--r--spec/frontend/search/sidebar/components/filters_spec.js132
-rw-r--r--spec/frontend/search/sidebar/components/scope_navigation_spec.js80
-rw-r--r--spec/frontend/search/store/actions_spec.js35
-rw-r--r--spec/frontend/search/store/mutations_spec.js22
-rw-r--r--spec/helpers/search_helper_spec.rb26
-rw-r--r--spec/models/ci/bridge_spec.rb14
-rw-r--r--spec/models/ci/processable_spec.rb6
-rw-r--r--spec/services/ci/retry_job_service_spec.rb44
-rw-r--r--spec/views/shared/deploy_tokens/_form.html.haml_spec.rb62
103 files changed, 1211 insertions, 732 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 022f1c17a93..e48159d67cb 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -39,17 +39,6 @@ review-docs-cleanup:
script:
- ./scripts/trigger-build.rb docs cleanup
-docs-lint markdown:
- extends:
- - .default-retry
- - .docs:rules:docs-lint
- # When updating the image version here, update it in /scripts/lint-doc.sh too.
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2
- stage: lint
- needs: []
- script:
- - scripts/lint-doc.sh
-
docs-lint links:
extends:
- .docs:rules:docs-lint
@@ -67,6 +56,33 @@ docs-lint links:
# Check the internal links and anchors (in parallel)
- "parallel time bundle exec nanoc check ::: internal_links internal_anchors"
+.docs-markdown-lint-image:
+ # When updating the image version here, update it in /scripts/lint-doc.sh too.
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2
+
+docs-lint markdown:
+ extends:
+ - .default-retry
+ - .docs:rules:docs-lint
+ - .docs-markdown-lint-image
+ stage: lint
+ needs: []
+ script:
+ - scripts/lint-doc.sh
+
+docs-code-quality:
+ extends:
+ - .docs:rules:docs-code-quality
+ - .docs-markdown-lint-image
+ stage: lint
+ needs: []
+ script:
+ - vale --output=doc/.vale/vale-json.tmpl --minAlertLevel warning doc/**/*.md > gl-code-quality-report-docs.json || exit_code=$?
+ artifacts:
+ reports:
+ codequality: gl-code-quality-report-docs.json
+ expire_in: 1 week
+
ui-docs-links lint:
extends:
- .docs:rules:docs-lint
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index 972412cc94a..1cb12363168 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -7,7 +7,7 @@ include:
- local: .gitlab/ci/package-and-test/rules.gitlab-ci.yml
- local: .gitlab/ci/package-and-test/variables.gitlab-ci.yml
- project: gitlab-org/quality/pipeline-common
- ref: 1.5.0
+ ref: 1.7.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index f567f896734..0745efff5b2 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -1,6 +1,6 @@
include:
- project: gitlab-org/quality/pipeline-common
- ref: 1.5.0
+ ref: 1.7.0
file:
- /ci/base.gitlab-ci.yml
- /ci/allure-report.yml
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 41c61483bc0..ab98b2a0591 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -213,6 +213,9 @@
- "scripts/lint-doc.sh"
- ".gitlab/ci/docs.gitlab-ci.yml"
+.docs-code-quality-patterns: &docs-code-quality-patterns
+ - "doc/**/*.md"
+
.docs-deprecations-and-removals-patterns: &docs-deprecations-and-removals-patterns
- "doc/update/deprecations.md"
- "doc/update/removals.md"
@@ -791,6 +794,12 @@
when: manual
allow_failure: true
+.docs:rules:docs-code-quality:
+ rules:
+ - <<: *if-default-branch-refs
+ - <<: *if-default-refs
+ changes: *docs-code-quality-patterns
+
.docs:rules:docs-lint:
rules:
- <<: *if-default-refs
diff --git a/.rubocop_todo/gitlab/json.yml b/.rubocop_todo/gitlab/json.yml
index 78c54dc157d..5b893710725 100644
--- a/.rubocop_todo/gitlab/json.yml
+++ b/.rubocop_todo/gitlab/json.yml
@@ -1,7 +1,6 @@
---
# Cop supports --autocorrect.
Gitlab/Json:
- Details: grace period
Exclude:
- 'app/controllers/admin/application_settings_controller.rb'
- 'app/controllers/concerns/authenticates_with_two_factor.rb'
diff --git a/.rubocop_todo/gitlab/no_code_coverage_comment.yml b/.rubocop_todo/gitlab/no_code_coverage_comment.yml
index 0dcee5f32ee..195dfe5a81d 100644
--- a/.rubocop_todo/gitlab/no_code_coverage_comment.yml
+++ b/.rubocop_todo/gitlab/no_code_coverage_comment.yml
@@ -1,6 +1,5 @@
---
Gitlab/NoCodeCoverageComment:
- Details: grace period
Exclude:
- 'app/models/integration.rb'
- 'app/services/ci/job_artifacts/destroy_batch_service.rb'
diff --git a/.rubocop_todo/gitlab/rspec/avoid_setup.yml b/.rubocop_todo/gitlab/rspec/avoid_setup.yml
deleted file mode 100644
index 5f75cda7a21..00000000000
--- a/.rubocop_todo/gitlab/rspec/avoid_setup.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-Gitlab/RSpec/AvoidSetup:
- Details: grace period
diff --git a/.rubocop_todo/gitlab/service_response.yml b/.rubocop_todo/gitlab/service_response.yml
index ccf934e09b3..03b73d6491d 100644
--- a/.rubocop_todo/gitlab/service_response.yml
+++ b/.rubocop_todo/gitlab/service_response.yml
@@ -1,6 +1,5 @@
---
Gitlab/ServiceResponse:
- Details: grace period
Exclude:
- 'app/services/alert_management/metric_images/upload_service.rb'
- 'app/services/analytics/cycle_analytics/stages/base_service.rb'
diff --git a/.rubocop_todo/layout/first_array_element_indentation.yml b/.rubocop_todo/layout/first_array_element_indentation.yml
index cbe806fc16f..d4a3d2f5524 100644
--- a/.rubocop_todo/layout/first_array_element_indentation.yml
+++ b/.rubocop_todo/layout/first_array_element_indentation.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Layout/FirstArrayElementIndentation:
- Details: grace period
Exclude:
- 'spec/lib/gitlab/github_import/importer/issues_importer_spec.rb'
- 'spec/lib/gitlab/search/found_blob_spec.rb'
diff --git a/.rubocop_todo/layout/trailing_whitespace.yml b/.rubocop_todo/layout/trailing_whitespace.yml
index d9c88c989e0..8e3e0795c03 100644
--- a/.rubocop_todo/layout/trailing_whitespace.yml
+++ b/.rubocop_todo/layout/trailing_whitespace.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Layout/TrailingWhitespace:
- Details: grace period
Exclude:
- 'app/models/concerns/analytics/cycle_analytics/stage_event_model.rb'
- 'db/migrate/20210611100359_rebuild_index_for_cadence_iterations_automation.rb'
diff --git a/.rubocop_todo/rails/active_record_callbacks_order.yml b/.rubocop_todo/rails/active_record_callbacks_order.yml
index 11ffff36e8d..baeba86c4b9 100644
--- a/.rubocop_todo/rails/active_record_callbacks_order.yml
+++ b/.rubocop_todo/rails/active_record_callbacks_order.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Rails/ActiveRecordCallbacksOrder:
- Details: grace period
Exclude:
- 'app/models/award_emoji.rb'
- 'app/models/gpg_key.rb'
diff --git a/.rubocop_todo/rails/file_path.yml b/.rubocop_todo/rails/file_path.yml
index 24a08fa5ee2..898d303bd3d 100644
--- a/.rubocop_todo/rails/file_path.yml
+++ b/.rubocop_todo/rails/file_path.yml
@@ -1,6 +1,5 @@
---
Rails/FilePath:
- Details: grace period
Exclude:
- 'app/controllers/help_controller.rb'
- 'app/helpers/startupjs_helper.rb'
diff --git a/.rubocop_todo/rails/helper_instance_variable.yml b/.rubocop_todo/rails/helper_instance_variable.yml
index 8f9197c9223..53e376730fd 100644
--- a/.rubocop_todo/rails/helper_instance_variable.yml
+++ b/.rubocop_todo/rails/helper_instance_variable.yml
@@ -1,6 +1,5 @@
---
Rails/HelperInstanceVariable:
- Details: grace period
Exclude:
- 'app/helpers/admin/user_actions_helper.rb'
- 'app/helpers/application_helper.rb'
diff --git a/.rubocop_todo/rails/index_with.yml b/.rubocop_todo/rails/index_with.yml
index 91a75e198f5..b7bc2a26959 100644
--- a/.rubocop_todo/rails/index_with.yml
+++ b/.rubocop_todo/rails/index_with.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Rails/IndexWith:
- Details: grace period
Exclude:
- 'app/helpers/ci/jobs_helper.rb'
- 'app/models/ci/build_trace_chunk.rb'
diff --git a/.rubocop_todo/rails/inverse_of.yml b/.rubocop_todo/rails/inverse_of.yml
index 262804739bd..2ad8d6204c8 100644
--- a/.rubocop_todo/rails/inverse_of.yml
+++ b/.rubocop_todo/rails/inverse_of.yml
@@ -1,6 +1,5 @@
---
Rails/InverseOf:
- Details: grace period
Exclude:
- 'app/models/alert_management/alert.rb'
- 'app/models/alert_management/alert_assignee.rb'
diff --git a/.rubocop_todo/rails/redundant_foreign_key.yml b/.rubocop_todo/rails/redundant_foreign_key.yml
index 22af6131b33..0d23c51caae 100644
--- a/.rubocop_todo/rails/redundant_foreign_key.yml
+++ b/.rubocop_todo/rails/redundant_foreign_key.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Rails/RedundantForeignKey:
- Details: grace period
Exclude:
- 'app/models/alert_management/metric_image.rb'
- 'app/models/ci/build.rb'
diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
index a2d5ba5d89b..90582e45009 100644
--- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml
+++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
@@ -1,6 +1,5 @@
---
RSpec/FactoryBot/AvoidCreate:
- Details: grace period
Exclude:
- 'ee/spec/presenters/approval_rule_presenter_spec.rb'
- 'ee/spec/presenters/audit_event_presenter_spec.rb'
diff --git a/.rubocop_todo/rspec/file_path.yml b/.rubocop_todo/rspec/file_path.yml
index ebeb967841c..8930b709bfd 100644
--- a/.rubocop_todo/rspec/file_path.yml
+++ b/.rubocop_todo/rspec/file_path.yml
@@ -1,6 +1,5 @@
---
RSpec/FilePath:
- Details: grace period
Exclude:
- 'ee/spec/frontend/fixtures/analytics/charts.rb'
- 'ee/spec/frontend/fixtures/analytics/devops_reports/devops_adoption/enabled_namespaces.rb'
diff --git a/.rubocop_todo/rspec/multiple_memoized_helpers.yml b/.rubocop_todo/rspec/multiple_memoized_helpers.yml
index 218ec3bb478..e939abd2886 100644
--- a/.rubocop_todo/rspec/multiple_memoized_helpers.yml
+++ b/.rubocop_todo/rspec/multiple_memoized_helpers.yml
@@ -1,6 +1,5 @@
---
RSpec/MultipleMemoizedHelpers:
- Details: grace period
Exclude:
- 'ee/spec/features/boards/swimlanes/epics_swimlanes_filtering_spec.rb'
- 'ee/spec/finders/epics_finder_spec.rb'
diff --git a/.rubocop_todo/rspec/scattered_let.yml b/.rubocop_todo/rspec/scattered_let.yml
index 61f1a09589f..9a272ec31cc 100644
--- a/.rubocop_todo/rspec/scattered_let.yml
+++ b/.rubocop_todo/rspec/scattered_let.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
RSpec/ScatteredLet:
- Details: grace period
Exclude:
- 'ee/spec/features/boards/user_visits_board_spec.rb'
- 'ee/spec/features/groups/group_roadmap_spec.rb'
diff --git a/.rubocop_todo/style/accessor_grouping.yml b/.rubocop_todo/style/accessor_grouping.yml
index a2ba217dbcc..a4fae856953 100644
--- a/.rubocop_todo/style/accessor_grouping.yml
+++ b/.rubocop_todo/style/accessor_grouping.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/AccessorGrouping:
- Details: grace period
Exclude:
- 'app/finders/template_finder.rb'
- 'app/models/commit.rb'
diff --git a/.rubocop_todo/style/bare_percent_literals.yml b/.rubocop_todo/style/bare_percent_literals.yml
index 1a155e3cca0..cb40669ca02 100644
--- a/.rubocop_todo/style/bare_percent_literals.yml
+++ b/.rubocop_todo/style/bare_percent_literals.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/BarePercentLiterals:
- Details: grace period
Exclude:
- 'app/models/commit.rb'
- 'app/models/concerns/storage/legacy_namespace.rb'
diff --git a/.rubocop_todo/style/empty_method.yml b/.rubocop_todo/style/empty_method.yml
index 7fbec98ead5..300d8678719 100644
--- a/.rubocop_todo/style/empty_method.yml
+++ b/.rubocop_todo/style/empty_method.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/EmptyMethod:
- Details: grace period
Exclude:
- 'app/controllers/admin/application_settings/appearances_controller.rb'
- 'app/controllers/admin/applications_controller.rb'
diff --git a/.rubocop_todo/style/explicit_block_argument.yml b/.rubocop_todo/style/explicit_block_argument.yml
index 346be201322..20e8c976fb7 100644
--- a/.rubocop_todo/style/explicit_block_argument.yml
+++ b/.rubocop_todo/style/explicit_block_argument.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/ExplicitBlockArgument:
- Details: grace period
Exclude:
- 'app/controllers/admin/background_migrations_controller.rb'
- 'app/controllers/admin/batched_jobs_controller.rb'
diff --git a/.rubocop_todo/style/keyword_parameters_order.yml b/.rubocop_todo/style/keyword_parameters_order.yml
index 7c283fed44a..ca6cb416b93 100644
--- a/.rubocop_todo/style/keyword_parameters_order.yml
+++ b/.rubocop_todo/style/keyword_parameters_order.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/KeywordParametersOrder:
- Details: grace period
Exclude:
- 'app/controllers/concerns/product_analytics_tracking.rb'
- 'app/finders/group_descendants_finder.rb'
diff --git a/.rubocop_todo/style/numeric_literal_prefix.yml b/.rubocop_todo/style/numeric_literal_prefix.yml
index 5de15180438..4e8b608e424 100644
--- a/.rubocop_todo/style/numeric_literal_prefix.yml
+++ b/.rubocop_todo/style/numeric_literal_prefix.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/NumericLiteralPrefix:
- Details: grace period
Exclude:
- 'app/models/container_repository.rb'
- 'app/services/packages/debian/generate_distribution_key_service.rb'
diff --git a/.rubocop_todo/style/redundant_begin.yml b/.rubocop_todo/style/redundant_begin.yml
index e96cdb26b08..d2851de201b 100644
--- a/.rubocop_todo/style/redundant_begin.yml
+++ b/.rubocop_todo/style/redundant_begin.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/RedundantBegin:
- Details: grace period
Exclude:
- 'app/controllers/concerns/membership_actions.rb'
- 'app/controllers/concerns/metrics_dashboard.rb'
diff --git a/.rubocop_todo/style/single_argument_dig.yml b/.rubocop_todo/style/single_argument_dig.yml
index 3ffd27d26ae..a85039a45f5 100644
--- a/.rubocop_todo/style/single_argument_dig.yml
+++ b/.rubocop_todo/style/single_argument_dig.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/SingleArgumentDig:
- Details: grace period
Exclude:
- 'app/graphql/resolvers/namespace_projects_resolver.rb'
- 'app/models/ci/build.rb'
diff --git a/.rubocop_todo/style/sole_nested_conditional.yml b/.rubocop_todo/style/sole_nested_conditional.yml
index 535b8d20765..3c663b5f89a 100644
--- a/.rubocop_todo/style/sole_nested_conditional.yml
+++ b/.rubocop_todo/style/sole_nested_conditional.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/SoleNestedConditional:
- Details: grace period
Exclude:
- 'app/controllers/admin/application_settings_controller.rb'
- 'app/controllers/ldap/omniauth_callbacks_controller.rb'
diff --git a/.rubocop_todo/style/special_global_vars.yml b/.rubocop_todo/style/special_global_vars.yml
deleted file mode 100644
index df688872d71..00000000000
--- a/.rubocop_todo/style/special_global_vars.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-# Cop supports --auto-correct.
-Style/SpecialGlobalVars:
- Details: grace period
diff --git a/.rubocop_todo/style/string_literals_in_interpolation.yml b/.rubocop_todo/style/string_literals_in_interpolation.yml
index fc90e1ce6a6..29e94e77345 100644
--- a/.rubocop_todo/style/string_literals_in_interpolation.yml
+++ b/.rubocop_todo/style/string_literals_in_interpolation.yml
@@ -1,7 +1,6 @@
---
# Cop supports --auto-correct.
Style/StringLiteralsInInterpolation:
- Details: grace period
Exclude:
- 'app/graphql/mutations/base_mutation.rb'
- 'app/helpers/colors_helper.rb'
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 44c16324950..f3307977be9 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -109,6 +109,8 @@ export default {
:update-filters="true"
:index="index"
:show-work-item-type-icon="showWorkItemTypeIcon"
- />
+ >
+ <slot></slot>
+ </board-card-inner>
</li>
</template>
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue
index 3a2b11a649d..1c67a2d9f7f 100644
--- a/app/assets/javascripts/boards/components/board_card_inner.vue
+++ b/app/assets/javascripts/boards/components/board_card_inner.vue
@@ -15,7 +15,6 @@ import { updateHistory } from '~/lib/utils/url_utility';
import { sprintf, __, n__ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
-import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import IssuableBlockedIcon from '~/vue_shared/components/issuable_blocked_icon/issuable_blocked_icon.vue';
import { ListType } from '../constants';
@@ -36,7 +35,6 @@ export default {
IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
IssuableBlockedIcon,
GlSprintf,
- BoardCardMoveToPosition,
WorkItemTypeIcon,
IssueHealthStatus: () =>
import('ee_component/related_items_tree/components/issue_health_status.vue'),
@@ -250,8 +248,7 @@ export default {
>{{ item.title }}</a
>
</h4>
- <!-- TODO: remove the condition when https://gitlab.com/gitlab-org/gitlab/-/issues/377862 is resolved -->
- <board-card-move-to-position v-if="!isEpicBoard" :item="item" :list="list" :index="index" />
+ <slot></slot>
</div>
<div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap">
<template v-for="label in orderedLabels">
diff --git a/app/assets/javascripts/boards/components/board_card_move_to_position.vue b/app/assets/javascripts/boards/components/board_card_move_to_position.vue
index ff938219475..706b453e868 100644
--- a/app/assets/javascripts/boards/components/board_card_move_to_position.vue
+++ b/app/assets/javascripts/boards/components/board_card_move_to_position.vue
@@ -1,6 +1,6 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { mapActions, mapGetters, mapState } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Tracking from '~/tracking';
@@ -31,10 +31,13 @@ export default {
type: Number,
required: true,
},
+ listItemsLength: {
+ type: Number,
+ required: true,
+ },
},
computed: {
...mapState(['pageInfoByListId']),
- ...mapGetters(['getBoardItemsByList']),
tracking() {
return {
category: 'boards:list',
@@ -42,15 +45,9 @@ export default {
property: `type_card`,
};
},
- listItems() {
- return this.getBoardItemsByList(this.list.id);
- },
listHasNextPage() {
return this.pageInfoByListId[this.list.id]?.hasNextPage;
},
- lengthOfListItemsInBoard() {
- return this.listItems?.length;
- },
itemIdentifier() {
return `${this.item.id}-${this.item.iid}-${this.index}`;
},
@@ -58,7 +55,7 @@ export default {
return this.index === 0;
},
isLastItemInList() {
- return this.index === this.lengthOfListItemsInBoard - 1;
+ return this.index === this.listItemsLength - 1;
},
},
methods: {
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index edf1a5ee7e6..ebf19c32e5e 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -7,6 +7,7 @@ import { defaultSortableOptions } from '~/sortable/constants';
import { sortableStart, sortableEnd } from '~/sortable/utils';
import Tracking from '~/tracking';
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
+import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
import { toggleFormEventPrefix, DraggableItemTypes } from '../constants';
import eventHub from '../eventhub';
import BoardCard from './board_card.vue';
@@ -27,6 +28,7 @@ export default {
BoardNewEpic: () => import('ee_component/boards/components/board_new_epic.vue'),
GlLoadingIcon,
GlIntersectionObserver,
+ BoardCardMoveToPosition,
},
mixins: [Tracking.mixin()],
props: {
@@ -309,7 +311,16 @@ export default {
:data-draggable-item-type="$options.draggableItemTypes.card"
:disabled="disabled"
:show-work-item-type-icon="!isEpicBoard"
- />
+ >
+ <!-- TODO: remove the condition when https://gitlab.com/gitlab-org/gitlab/-/issues/377862 is resolved -->
+ <board-card-move-to-position
+ v-if="!isEpicBoard"
+ :item="item"
+ :index="index"
+ :list="list"
+ :list-items-length="boardItems.length"
+ />
+ </board-card>
<gl-intersection-observer @appear="onReachingListBottom">
<li
v-if="showCount"
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue
index 789efc8f09d..927ae6f6b81 100644
--- a/app/assets/javascripts/search/sidebar/components/app.vue
+++ b/app/assets/javascripts/search/sidebar/components/app.vue
@@ -1,48 +1,27 @@
<script>
-import { GlButton, GlLink } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
-import ConfidentialityFilter from './confidentiality_filter.vue';
-import StatusFilter from './status_filter.vue';
+import { mapState } from 'vuex';
+import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue';
+import { SCOPE_ISSUES, SCOPE_MERGE_REQUESTS } from '../constants';
+import ResultsFilters from './results_filters.vue';
export default {
name: 'GlobalSearchSidebar',
components: {
- GlButton,
- GlLink,
- StatusFilter,
- ConfidentialityFilter,
+ ResultsFilters,
+ ScopeNavigation,
},
computed: {
- ...mapState(['urlQuery', 'sidebarDirty']),
- showReset() {
- return this.urlQuery.state || this.urlQuery.confidential;
+ ...mapState(['urlQuery']),
+ showFilters() {
+ return this.urlQuery.scope === SCOPE_ISSUES || this.urlQuery.scope === SCOPE_MERGE_REQUESTS;
},
- showSidebar() {
- return this.urlQuery.scope === 'issues' || this.urlQuery.scope === 'merge_requests';
- },
- },
- methods: {
- ...mapActions(['applyQuery', 'resetQuery']),
},
};
</script>
<template>
- <form
- class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5"
- @submit.prevent="applyQuery"
- >
- <template v-if="showSidebar">
- <status-filter />
- <confidentiality-filter />
- <div class="gl-display-flex gl-align-items-center gl-mt-3">
- <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
- {{ __('Apply') }}
- </gl-button>
- <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{
- __('Reset filters')
- }}</gl-link>
- </div>
- </template>
- </form>
+ <section class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5">
+ <scope-navigation />
+ <results-filters v-if="showFilters" />
+ </section>
</template>
diff --git a/app/assets/javascripts/search/sidebar/components/results_filters.vue b/app/assets/javascripts/search/sidebar/components/results_filters.vue
new file mode 100644
index 00000000000..5b53f94bb53
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/results_filters.vue
@@ -0,0 +1,49 @@
+<script>
+import { GlButton, GlLink } from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ConfidentialityFilter from './confidentiality_filter.vue';
+import StatusFilter from './status_filter.vue';
+
+export default {
+ name: 'ResultsFilters',
+ components: {
+ GlButton,
+ GlLink,
+ StatusFilter,
+ ConfidentialityFilter,
+ },
+ mixins: [glFeatureFlagsMixin()],
+ computed: {
+ ...mapState(['urlQuery', 'sidebarDirty']),
+ showReset() {
+ return this.urlQuery.state || this.urlQuery.confidential;
+ },
+ searchPageVerticalNavFeatureFlag() {
+ return this.glFeatures.searchPageVerticalNav;
+ },
+ },
+ methods: {
+ ...mapActions(['applyQuery', 'resetQuery']),
+ },
+};
+</script>
+
+<template>
+ <form
+ :class="searchPageVerticalNavFeatureFlag ? 'gl-px-5' : 'gl-px-0'"
+ @submit.prevent="applyQuery"
+ >
+ <hr v-if="searchPageVerticalNavFeatureFlag" class="gl-my-5 gl-border-gray-100" />
+ <status-filter />
+ <confidentiality-filter />
+ <div class="gl-display-flex gl-align-items-center gl-mt-4">
+ <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
+ {{ __('Apply') }}
+ </gl-button>
+ <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{
+ __('Reset filters')
+ }}</gl-link>
+ </div>
+ </form>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
new file mode 100644
index 00000000000..37138955415
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlNav, GlNavItem } from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
+import { formatNumber } from '~/locale';
+import Tracking from '~/tracking';
+import { NAV_LINK_DEFAULT_CLASSES, NUMBER_FORMATING_OPTIONS } from '../constants';
+
+export default {
+ name: 'ScopeNavigation',
+ components: {
+ GlNav,
+ GlNavItem,
+ },
+ mixins: [Tracking.mixin()],
+ computed: {
+ ...mapState(['navigation', 'urlQuery']),
+ },
+ created() {
+ this.fetchSidebarCount();
+ },
+ methods: {
+ ...mapActions(['fetchSidebarCount']),
+ activeClasses(currentScope) {
+ return currentScope === this.urlQuery.scope ? 'gl-font-weight-bold' : '';
+ },
+ showFormatedCount(count) {
+ if (!count) {
+ return '0';
+ }
+ const countNumber = parseInt(count.replace(/,/g, ''), 10);
+ return formatNumber(countNumber, NUMBER_FORMATING_OPTIONS);
+ },
+ handleClick(scope) {
+ this.track('click_menu_item', { label: `vertical_navigation_${scope}` });
+ },
+ linkClasses(scope) {
+ return [
+ { 'gl-font-weight-bold': scope === this.urlQuery.scope },
+ ...this.$options.NAV_LINK_DEFAULT_CLASSES,
+ ];
+ },
+ },
+ NAV_LINK_DEFAULT_CLASSES,
+};
+</script>
+
+<template>
+ <nav>
+ <gl-nav vertical pills>
+ <gl-nav-item
+ v-for="(item, scope, index) in navigation"
+ :key="scope"
+ :link-classes="linkClasses(scope)"
+ class="gl-mb-1"
+ :href="item.link"
+ :active="urlQuery.scope ? urlQuery.scope === scope : index === 0"
+ @click="handleClick(scope)"
+ ><span>{{ item.label }}</span
+ ><span v-if="item.count" class="gl-font-sm gl-font-weight-normal">
+ {{ showFormatedCount(item.count) }}
+ </span>
+ </gl-nav-item>
+ </gl-nav>
+ <hr class="gl-mt-5 gl-mb-0 gl-border-gray-100 gl-md-display-none" />
+ </nav>
+</template>
diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js
new file mode 100644
index 00000000000..3621138afe4
--- /dev/null
+++ b/app/assets/javascripts/search/sidebar/constants/index.js
@@ -0,0 +1,11 @@
+export const SCOPE_ISSUES = 'issues';
+export const SCOPE_MERGE_REQUESTS = 'merge_requests';
+
+export const NUMBER_FORMATING_OPTIONS = { notation: 'compact', compactDisplay: 'short' };
+export const NAV_LINK_DEFAULT_CLASSES = [
+ 'gl-display-flex',
+ 'gl-flex-direction-row',
+ 'gl-flex-wrap-nowrap',
+ 'gl-justify-content-space-between',
+ 'gl-text-gray-900',
+];
diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js
index be5742e5949..2a1b744561d 100644
--- a/app/assets/javascripts/search/store/actions.js
+++ b/app/assets/javascripts/search/store/actions.js
@@ -1,6 +1,8 @@
import Api from '~/api';
import { createAlert } from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
+import { logError } from '~/lib/logger';
import { __ } from '~/locale';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants';
import * as types from './mutation_types';
@@ -99,3 +101,19 @@ export const applyQuery = ({ state }) => {
export const resetQuery = ({ state }) => {
visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null }));
};
+
+export const fetchSidebarCount = ({ commit, state }) => {
+ const promises = Object.keys(state.navigation).map((scope) => {
+ // active nav item has count already so we skip it
+ if (scope !== state.urlQuery.scope) {
+ return axios
+ .get(state.navigation[scope].count_link)
+ .then(({ data: { count } }) => {
+ commit(types.RECEIVE_NAVIGATION_COUNT, { key: scope, count });
+ })
+ .catch((e) => logError(e));
+ }
+ return Promise.resolve();
+ });
+ return Promise.all(promises);
+};
diff --git a/app/assets/javascripts/search/store/index.js b/app/assets/javascripts/search/store/index.js
index 4fa88822722..e20a43808cf 100644
--- a/app/assets/javascripts/search/store/index.js
+++ b/app/assets/javascripts/search/store/index.js
@@ -7,11 +7,11 @@ import createState from './state';
Vue.use(Vuex);
-export const getStoreConfig = ({ query }) => ({
+export const getStoreConfig = ({ query, navigation }) => ({
actions,
getters,
mutations,
- state: createState({ query }),
+ state: createState({ query, navigation }),
});
const createStore = (config) => new Vuex.Store(getStoreConfig(config));
diff --git a/app/assets/javascripts/search/store/mutation_types.js b/app/assets/javascripts/search/store/mutation_types.js
index bf1e3e79cba..511b93cad2b 100644
--- a/app/assets/javascripts/search/store/mutation_types.js
+++ b/app/assets/javascripts/search/store/mutation_types.js
@@ -10,3 +10,4 @@ export const SET_QUERY = 'SET_QUERY';
export const SET_SIDEBAR_DIRTY = 'SET_SIDEBAR_DIRTY';
export const LOAD_FREQUENT_ITEMS = 'LOAD_FREQUENT_ITEMS';
+export const RECEIVE_NAVIGATION_COUNT = 'RECEIVE_NAVIGATION_COUNT';
diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js
index 5d154fe3aa0..c1339845272 100644
--- a/app/assets/javascripts/search/store/mutations.js
+++ b/app/assets/javascripts/search/store/mutations.js
@@ -32,4 +32,8 @@ export default {
[types.LOAD_FREQUENT_ITEMS](state, { key, data }) {
state.frequentItems[key] = data;
},
+ [types.RECEIVE_NAVIGATION_COUNT](state, { key, count }) {
+ const item = { ...state.navigation[key], count };
+ state.navigation = { ...state.navigation, [key]: item };
+ },
};
diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js
index d4005697f35..b64231a8688 100644
--- a/app/assets/javascripts/search/store/state.js
+++ b/app/assets/javascripts/search/store/state.js
@@ -1,7 +1,7 @@
import { cloneDeep } from 'lodash';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
-const createState = ({ query }) => ({
+const createState = ({ query, navigation }) => ({
urlQuery: cloneDeep(query),
query,
groups: [],
@@ -13,5 +13,6 @@ const createState = ({ query }) => ({
[PROJECTS_LOCAL_STORAGE_KEY]: [],
},
sidebarDirty: false,
+ navigation,
});
export default createState;
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index d644d8acc98..f37b426cd91 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -219,6 +219,16 @@
}
}
+ .search-sidebar {
+ .nav-link {
+ &.active,
+ &:hover {
+ background-color: rgba($gray-50, 0.8);
+ color: $gray-900;
+ }
+ }
+ }
+
// Sidebar
.nav-sidebar li.active > a {
color: $gray-900;
diff --git a/app/controllers/groups/settings/repository_controller.rb b/app/controllers/groups/settings/repository_controller.rb
index cb62ea2a543..ecd5d814fb6 100644
--- a/app/controllers/groups/settings/repository_controller.rb
+++ b/app/controllers/groups/settings/repository_controller.rb
@@ -8,9 +8,6 @@ module Groups
before_action :authorize_create_deploy_token!, only: :create_deploy_token
before_action :authorize_access!, only: :show
before_action :define_deploy_token_variables, if: -> { can?(current_user, :create_deploy_token, @group) }
- before_action do
- push_frontend_feature_flag(:ajax_new_deploy_token, @group)
- end
feature_category :continuous_delivery
urgency :low
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 40e89a06b46..c1449dfd3d3 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -26,12 +26,6 @@ class Projects::ArtifactsController < Projects::ApplicationController
# It should be removed only after resolving the underlying performance
# issues: https://gitlab.com/gitlab-org/gitlab/issues/32281
return head :no_content unless Feature.enabled?(:artifacts_management_page, @project)
-
- finder = Ci::JobArtifactsFinder.new(@project, artifacts_params)
- all_artifacts = finder.execute
-
- @artifacts = all_artifacts.page(params[:page]).per(MAX_PER_PAGE)
- @total_size = all_artifacts.total_size
end
def destroy
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 6ad85d8a47c..bf231bf012d 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -12,7 +12,6 @@ module Projects
before_action :check_builds_available!
before_action :define_variables
before_action do
- push_frontend_feature_flag(:ajax_new_deploy_token, @project)
push_frontend_feature_flag(:ci_variable_settings_graphql, @project)
end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index 0bf9b05b3b5..90988645d3a 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -6,9 +6,6 @@ module Projects
layout 'project_settings'
before_action :authorize_admin_project!
before_action :define_variables, only: [:create_deploy_token]
- before_action do
- push_frontend_feature_flag(:ajax_new_deploy_token, @project)
- end
feature_category :source_code_management, [:show, :cleanup, :update]
feature_category :continuous_delivery, [:create_deploy_token]
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index b3f82e89dfa..2f3aba8d09d 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -417,7 +417,11 @@ module SearchHelper
active_scope = @scope == scope_name
result = { label: label, scope: scope_name, data: data, link: search_path(search_params), active: active_scope }
- result[:count] = @search_results.formatted_count(scope_name) if active_scope && !@timeout
+
+ if active_scope
+ result[:count] = !@timeout ? @search_results.formatted_count(scope_name) : 0
+ end
+
result[:count_link] = search_count_path(search_params) unless active_scope
result
@@ -426,21 +430,24 @@ module SearchHelper
# search page scope navigation
def search_navigation
{
- projects: { label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
- blobs: { label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || search_service.show_elasticsearch_tabs? || feature_flag_tab_enabled?(:global_search_code_tab) },
- issues: { label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
- merge_requests: { label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
- wiki_blobs: { label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
- commits: { label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
- notes: { label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
- milestones: { label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
- users: { label: _("Users"), condition: show_user_search_tab? },
- snippet_titles: { label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
+ projects: { sort: 1, label: _("Projects"), data: { qa_selector: 'projects_tab' }, condition: @project.nil? },
+ blobs: { sort: 2, label: _("Code"), data: { qa_selector: 'code_tab' }, condition: project_search_tabs?(:blobs) || search_service.show_elasticsearch_tabs? || feature_flag_tab_enabled?(:global_search_code_tab) },
+ # sort: 3 is reserved for EE items
+ issues: { sort: 4, label: _("Issues"), condition: project_search_tabs?(:issues) || feature_flag_tab_enabled?(:global_search_issues_tab) },
+ merge_requests: { sort: 5, label: _("Merge requests"), condition: project_search_tabs?(:merge_requests) || feature_flag_tab_enabled?(:global_search_merge_requests_tab) },
+ wiki_blobs: { sort: 6, label: _("Wiki"), condition: project_search_tabs?(:wiki) || search_service.show_elasticsearch_tabs? },
+ commits: { sort: 7, label: _("Commits"), condition: project_search_tabs?(:commits) || (search_service.show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_commits_tab)) },
+ notes: { sort: 8, label: _("Comments"), condition: project_search_tabs?(:notes) || search_service.show_elasticsearch_tabs? },
+ milestones: { sort: 9, label: _("Milestones"), condition: project_search_tabs?(:milestones) || @project.nil? },
+ users: { sort: 10, label: _("Users"), condition: show_user_search_tab? },
+ snippet_titles: { sort: 11, label: _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }, condition: @show_snippets.present? && @project.nil? }
}
end
def search_navigation_json
- Gitlab::Json.dump(search_navigation.each_with_object({}) do |(key, value), hash|
+ sorted_navigation = search_navigation.sort_by { |_, h| h[:sort] }
+
+ Gitlab::Json.dump(sorted_navigation.each_with_object({}) do |(key, value), hash|
hash[key] = search_filter_link_json(key, value[:label], value[:data], value[:search]) if value[:condition]
end)
end
diff --git a/app/models/ci/bridge.rb b/app/models/ci/bridge.rb
index b2bdbda9da9..e1fbf7e3944 100644
--- a/app/models/ci/bridge.rb
+++ b/app/models/ci/bridge.rb
@@ -58,11 +58,7 @@ module Ci
end
def retryable?
- return false unless Feature.enabled?(:ci_recreate_downstream_pipeline, project)
-
- return false if failed? && (pipeline_loop_detected? || reached_max_descendant_pipelines_depth?)
-
- super
+ false
end
def self.with_preloads
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 8262c3c90e1..ea2ea92dfce 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -1,7 +1,7 @@
- search_bar_classes = 'search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4'
= render_if_exists 'shared/promotions/promote_advanced_search'
-- if Feature.enabled?(:search_page_vertical_nav, current_user) && %w[issues merge_requests].include?(@scope)
+- if Feature.enabled?(:search_page_vertical_nav, current_user)
.results.gl-md-display-flex.gl-mt-0
#js-search-sidebar{ class: search_bar_classes, data: { navigation: search_navigation_json } }
.gl-w-full.gl-flex-grow-1.gl-overflow-x-hidden
diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml
index 1b48843eb10..0f290f34a95 100644
--- a/app/views/shared/deploy_tokens/_form.html.haml
+++ b/app/views/shared/deploy_tokens/_form.html.haml
@@ -3,7 +3,7 @@
- group_deploy_tokens_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_deploy_tokens_help_link_url }
= s_('DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}').html_safe % { link_start: group_deploy_tokens_help_link_start, link_end: '</a>'.html_safe }
-= gitlab_ui_form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f|
+= gitlab_ui_form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: true do |f|
.form-group
= f.label :name, class: 'label-bold'
diff --git a/app/views/shared/deploy_tokens/_index.html.haml b/app/views/shared/deploy_tokens/_index.html.haml
index faec379e42b..e5f1fd99125 100644
--- a/app/views/shared/deploy_tokens/_index.html.haml
+++ b/app/views/shared/deploy_tokens/_index.html.haml
@@ -8,20 +8,13 @@
%p
= description
.settings-content
- - if Feature.enabled?(:ajax_new_deploy_token, group_or_project)
- #js-new-deploy-token{ data: {
- container_registry_enabled: container_registry_enabled?(group_or_project),
- packages_registry_enabled: packages_registry_enabled?(group_or_project),
- create_new_token_path: create_deploy_token_path(group_or_project),
- token_type: group_or_project.is_a?(Group) ? 'group' : 'project',
- deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index.md')
- }
+ #js-new-deploy-token{ data: {
+ container_registry_enabled: container_registry_enabled?(group_or_project),
+ packages_registry_enabled: packages_registry_enabled?(group_or_project),
+ create_new_token_path: create_deploy_token_path(group_or_project),
+ token_type: group_or_project.is_a?(Group) ? 'group' : 'project',
+ deploy_tokens_help_url: help_page_path('user/project/deploy_tokens/index.md')
}
- - else
- - if @created_deploy_token
- = render 'shared/deploy_tokens/new_deploy_token', deploy_token: @created_deploy_token
- %h5.gl-mt-0
- = s_('DeployTokens|New deploy token')
- = render 'shared/deploy_tokens/form', group_or_project: group_or_project, token: @new_deploy_token, presenter: @deploy_tokens
+ }
%hr
= render 'shared/deploy_tokens/table', group_or_project: group_or_project, active_tokens: @deploy_tokens
diff --git a/config/feature_flags/development/ci_recreate_downstream_pipeline.yml b/config/feature_flags/development/ai_assist_api.yml
index 17b8a0965fc..9b7da480f62 100644
--- a/config/feature_flags/development/ci_recreate_downstream_pipeline.yml
+++ b/config/feature_flags/development/ai_assist_api.yml
@@ -1,8 +1,8 @@
---
-name: ci_recreate_downstream_pipeline
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83613
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358409
-milestone: '14.10'
+name: ai_assist_api
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100500
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378470
+milestone: '15.6'
type: development
-group: group::pipeline authoring
+group: group::incubation
default_enabled: false
diff --git a/config/feature_flags/development/ai_assist_flag.yml b/config/feature_flags/development/ai_assist_flag.yml
new file mode 100644
index 00000000000..ce8bff8267c
--- /dev/null
+++ b/config/feature_flags/development/ai_assist_flag.yml
@@ -0,0 +1,8 @@
+---
+name: ai_assist_flag
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100500
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378470
+milestone: '15.6'
+type: development
+group: group::incubation
+default_enabled: false
diff --git a/config/feature_flags/development/ajax_new_deploy_token.yml b/config/feature_flags/development/ajax_new_deploy_token.yml
deleted file mode 100644
index 6b0d9697006..00000000000
--- a/config/feature_flags/development/ajax_new_deploy_token.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ajax_new_deploy_token
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27141
-rollout_issue_url:
-milestone: '12.10'
-type: development
-group: group::release
-default_enabled: false
diff --git a/config/metrics/counts_28d/20220621085114_unique_active_users_monthly.yml b/config/metrics/counts_28d/20220621085114_unique_active_users_monthly.yml
index 3282a4db173..06b1cbcc3cc 100644
--- a/config/metrics/counts_28d/20220621085114_unique_active_users_monthly.yml
+++ b/config/metrics/counts_28d/20220621085114_unique_active_users_monthly.yml
@@ -10,7 +10,7 @@ milestone: "15.2"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90468/
time_frame: 28d
data_source: redis_hll
-data_category: optional
+data_category: operational
instrumentation_class: RedisHLLMetric
options:
events:
diff --git a/config/metrics/counts_all/20210216183000_package_events_i_package_pull_package_by_guest.yml b/config/metrics/counts_all/20210216183000_package_events_i_package_pull_package_by_guest.yml
index 45dcd531484..1ad33d0d479 100644
--- a/config/metrics/counts_all/20210216183000_package_events_i_package_pull_package_by_guest.yml
+++ b/config/metrics/counts_all/20210216183000_package_events_i_package_pull_package_by_guest.yml
@@ -1,5 +1,5 @@
---
-data_category: optional
+data_category: operational
key_path: counts.package_events_i_package_pull_package_by_guest
description: A count of packages that have been downloaded from the package registry
by a guest
diff --git a/config/metrics/counts_all/20210216183005_package_events_i_package_push_package_by_deploy_token.yml b/config/metrics/counts_all/20210216183005_package_events_i_package_push_package_by_deploy_token.yml
index 252474155c1..051e99a67e4 100644
--- a/config/metrics/counts_all/20210216183005_package_events_i_package_push_package_by_deploy_token.yml
+++ b/config/metrics/counts_all/20210216183005_package_events_i_package_push_package_by_deploy_token.yml
@@ -1,5 +1,5 @@
---
-data_category: optional
+data_category: operational
key_path: counts.package_events_i_package_push_package_by_deploy_token
description: A count of packages that have been published to the package registry
using a deploy token
diff --git a/config/open_api.yml b/config/open_api.yml
index 785bf43509f..a73f4916ae5 100644
--- a/config/open_api.yml
+++ b/config/open_api.yml
@@ -41,6 +41,10 @@ metadata:
description: Operations related to managing Flipper-based feature flags
- name: freeze_periods
description: Operations related to deploy freeze periods
+ - name: ci_lint
+ description: Operations related to linting a CI config file
+ - name: group_export
+ description: Operations related to export groups
- name: merge_requests
description: Operations related to merge requests
- name: metadata
diff --git a/doc/.vale/vale-json.tmpl b/doc/.vale/vale-json.tmpl
index f76ca03964c..7969cb704a0 100644
--- a/doc/.vale/vale-json.tmpl
+++ b/doc/.vale/vale-json.tmpl
@@ -1,47 +1,28 @@
{{- /* Modify Vale's output https://docs.errata.ai/vale/cli#--output */ -}}
-{{- /* Keep track of our various counts */ -}}
+{{- $fileIndexes := len .Files -}}
+{{- $fileIndexes = sub $fileIndexes 1 -}}
-{{- $e := 0 -}}
-{{- $w := 0 -}}
-{{- $s := 0 -}}
-{{- $f := 0 -}}
-
-{{- /* Range over the linted files */ -}}
-
-{{- range .Files}}
-
-{{- $f = add1 $f -}}
-{{- $path := .Path -}}
-
-{{- /* Range over the file's alerts */ -}}
[
-
-{{- range $idx, $a := .Alerts -}}
-
-{{- $error := "" -}}
-{{- if eq .Severity "error" -}}
- {{- $error = "blocker" -}}
- {{- $e = add1 $e -}}
-{{- else if eq .Severity "warning" -}}
- {{- $error = "major" -}}
- {{- $w = add1 $w -}}
-{{- else -}}
- {{- $error ="info" -}}
- {{- $s = add1 $s -}}
-{{- end}}
-
-{{- /* Variables setup */ -}}
-
-{{- $path = $path -}}
-{{- $loc := printf "%d" .Line -}}
-{{- $check := printf "%s" .Check -}}
-{{- $message := printf "%s" .Message -}}
-{{- $link := printf "%s" .Link -}}
-{{- if $idx -}},{{- end -}}
-
-{{- /* Output */ -}}
-
+ {{- /* Range over the linted files */ -}}
+ {{- range $idx1, $a := .Files -}}
+ {{- $path := .Path -}}
+
+ {{/* Range over the file's alerts */}}
+ {{- range $idx2, $b := .Alerts -}}
+ {{- $error := "info" -}}
+ {{- if eq .Severity "error" -}}
+ {{- $error = "blocker" -}}
+ {{- else if eq .Severity "warning" -}}
+ {{- $error = "major" -}}
+ {{- end}}
+
+ {{- /* Variables setup */ -}}
+ {{- $loc := printf "%d" .Line -}}
+ {{- $message := printf "%s" .Message -}}
+ {{- if $idx2 -}},{{- end -}}
+
+ {{/* Output */}}
{
"description": "{{ $message }}",
"fingerprint": "{{ $path }}-{{ $loc }}",
@@ -53,6 +34,6 @@
}
}
}
-{{end -}}
-{{end -}}
+ {{- end}}{{- if (lt $idx1 $fileIndexes) -}},{{- end -}}
+ {{- end}}
]
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index b40df01cbe9..9b70181cbf1 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -38,6 +38,10 @@ and see the [Development section](../../index.md) for the required guidelines.
If you find an issue, please submit a merge request with a fix or improvement,
if you can, and include tests.
+NOTE:
+Consider placing your code behind a feature flag if you think it might affect production availability.
+Not sure? Read [When to use feature flags](https://about.gitlab.com/handbook/product-development-flow/feature-flag-lifecycle/#when-to-use-feature-flags).
+
If the change is non-trivial, we encourage you to
start a discussion with [a product manager or a member of the team](https://about.gitlab.com/handbook/product/categories/).
You can do
diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md
index e63fdb740f1..61aa5c5fe02 100644
--- a/doc/user/group/epics/manage_epics.md
+++ b/doc/user/group/epics/manage_epics.md
@@ -492,20 +492,42 @@ Epics can contain multiple nested child epics, up to a total of 7 levels deep.
The maximum number of direct child epics is 100.
+### Child epics from other groups
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/8502) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `child_epics_from_different_hierarchies`.
+On GitLab.com, this feature is not available. The feature is not ready for production use.
+
+You can add a child epic that belongs to a group that is different from the parent epic's group.
+
+Prerequisites:
+
+- You must have at least the Reporter role for both the child and parent epics' groups.
+- Multi-level child epics must be available for both the child and parent epics' groups.
+
+To add a child epic from another group, paste the epic's URL when [adding an existing epic](#add-a-child-epic-to-an-epic).
+
### Add a child epic to an epic
Prerequisites:
- You must have at least the Reporter role for the parent epic's group.
-To add a child epic to an epic:
+To add a new epic as child epic:
-1. Select **Add**.
-1. Select **Add a new epic**.
+1. In an epic, in the **Child issues and epics** section, select **Add > Add a new epic**.
+1. Select a group from the dropdown. The epic's group is selected by default.
+1. Enter a title for the new epic.
+1. Select **Create epic**.
+
+To add an existing epic as child epic:
+
+1. In an epic, in the **Child issues and epics** section, select **Add > Add an existing epic**.
1. Identify the epic to be added, using either of the following methods:
- Paste the link of the epic.
- - Search for the desired issue by entering part of the epic's title, then selecting the desired
- match ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9126) in GitLab 12.5).
+ - Search for the desired issue by entering part of the epic's title, then selecting the desired match. This search is only available for epics within the same group hierarchy.
If there are multiple epics to be added, press <kbd>Space</kbd> and repeat this step.
1. Select **Add**.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 222b6d44cfd..32a4f834908 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -469,7 +469,7 @@ project and should only have access to that project.
External users:
-- Cannot create project, groups, and snippets within their personal namespaces.
+- Cannot create project, groups, and snippets in their personal namespaces.
- Can only create projects (including forks), subgroups, and snippets within top-level groups to which they are explicitly granted access.
- Can only access public projects and projects to which they are explicitly granted access,
thus hiding all other internal or private ones from them (like being
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 19764885769..ee1e6e05e26 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -181,6 +181,7 @@ module API
mount ::API::Ci::Runners
mount ::API::Clusters::AgentTokens
mount ::API::Clusters::Agents
+ mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::DeployKeys
mount ::API::DeployTokens
@@ -190,9 +191,11 @@ module API
mount ::API::FeatureFlagsUserLists
mount ::API::Features
mount ::API::FreezePeriods
+ mount ::API::GroupExport
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::Keys
+ mount ::API::Lint
mount ::API::MergeRequestDiffs
mount ::API::Metadata
mount ::API::PersonalAccessTokens::SelfInformation
@@ -206,6 +209,7 @@ module API
mount ::API::ProtectedTags
mount ::API::Releases
mount ::API::Release::Links
+ mount ::API::Repositories
mount ::API::ResourceAccessTokens
mount ::API::Snippets
mount ::API::SnippetRepositoryStorageMoves
@@ -239,7 +243,6 @@ module API
mount ::API::Ci::Triggers
mount ::API::Ci::Variables
mount ::API::CommitStatuses
- mount ::API::Commits
mount ::API::ComposerPackages
mount ::API::ConanInstancePackages
mount ::API::ConanProjectPackages
@@ -262,7 +265,6 @@ module API
mount ::API::GroupClusters
mount ::API::GroupContainerRepositories
mount ::API::GroupDebianDistributions
- mount ::API::GroupExport
mount ::API::GroupImport
mount ::API::GroupLabels
mount ::API::GroupMilestones
@@ -276,7 +278,6 @@ module API
mount ::API::IssueLinks
mount ::API::Issues
mount ::API::Labels
- mount ::API::Lint
mount ::API::Markdown
mount ::API::MavenPackages
mount ::API::Members
@@ -307,7 +308,6 @@ module API
mount ::API::ProtectedTags
mount ::API::PypiPackages
mount ::API::RemoteMirrors
- mount ::API::Repositories
mount ::API::ResourceLabelEvents
mount ::API::ResourceMilestoneEvents
mount ::API::ResourceStateEvents
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index b141e3edc71..3b122fb23a2 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -31,13 +31,31 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, urgency: :low do
desc 'Get a project repository commits' do
- success Entities::Commit
+ success code: 200, model: Entities::Commit
+ tags %w[commits]
+ is_array true
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 404, message: 'Not found' }
+ ]
end
params do
- optional :ref_name, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
- optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
- optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
- optional :path, type: String, desc: 'The file path'
+ optional :ref_name,
+ type: String,
+ desc: 'The name of a repository branch or tag, if not given the default branch is used',
+ documentation: { example: 'v1.1.0' }
+ optional :since,
+ type: DateTime,
+ desc: 'Only commits after or on this date will be returned',
+ documentation: { example: '2021-09-20T11:50:22.001' }
+ optional :until,
+ type: DateTime,
+ desc: 'Only commits before or on this date will be returned',
+ documentation: { example: '2021-09-20T11:50:22.001' }
+ optional :path,
+ type: String,
+ desc: 'The file path',
+ documentation: { example: 'README.md' }
optional :all, type: Boolean, desc: 'Every commit will be returned'
optional :with_stats, type: Boolean, desc: 'Stats about each commit will be added to the response'
optional :first_parent, type: Boolean, desc: 'Only include the first parent of merges'
@@ -81,40 +99,87 @@ module API
end
desc 'Commit multiple file changes as one commit' do
- success Entities::CommitDetail
+ success code: 200, model: Entities::CommitDetail
+ tags %w[commits]
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 404, message: 'Not found' }
+ ]
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false
- requires :commit_message, type: String, desc: 'Commit message'
- requires :actions, type: Array, desc: 'Actions to perform in commit' do
- requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze, allow_blank: false
- requires :file_path, type: String, desc: 'Full path to the file. Ex. `lib/class.rb`'
+ requires :branch,
+ type: String,
+ desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.',
+ allow_blank: false,
+ documentation: { example: 'master' }
+ requires :commit_message,
+ type: String,
+ desc: 'Commit message',
+ documentation: { example: 'initial commit' }
+ requires :actions,
+ type: Array,
+ desc: 'Actions to perform in commit' do
+ requires :action,
+ type: String,
+ desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze,
+ allow_blank: false
+ requires :file_path,
+ type: String,
+ desc: 'Full path to the file.',
+ documentation: { example: 'lib/class.rb' }
given action: ->(action) { action == 'move' } do
- requires :previous_path, type: String, desc: 'Original full path to the file being moved. Ex. `lib/class1.rb`'
+ requires :previous_path,
+ type: String,
+ desc: 'Original full path to the file being moved.',
+ documentation: { example: 'lib/class.rb' }
end
given action: ->(action) { %w[create move].include? action } do
- optional :content, type: String, desc: 'File content'
+ optional :content,
+ type: String,
+ desc: 'File content',
+ documentation: { example: 'Some file content' }
end
given action: ->(action) { action == 'update' } do
- requires :content, type: String, desc: 'File content'
+ requires :content,
+ type: String,
+ desc: 'File content',
+ documentation: { example: 'Some file content' }
end
optional :encoding, type: String, desc: '`text` or `base64`', default: 'text', values: %w[text base64]
given action: ->(action) { %w[update move delete].include? action } do
- optional :last_commit_id, type: String, desc: 'Last known file commit id'
+ optional :last_commit_id,
+ type: String,
+ desc: 'Last known file commit id',
+ documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' }
end
given action: ->(action) { action == 'chmod' } do
requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.'
end
end
- optional :start_branch, type: String, desc: 'Name of the branch to start the new branch from'
- optional :start_sha, type: String, desc: 'SHA of the commit to start the new branch from'
+ optional :start_branch,
+ type: String,
+ desc: 'Name of the branch to start the new branch from',
+ documentation: { example: 'staging' }
+ optional :start_sha,
+ type: String,
+ desc: 'SHA of the commit to start the new branch from',
+ documentation: { example: '2695effb5807a22ff3d138d593fd856244e155e7' }
mutually_exclusive :start_branch, :start_sha
- optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the new branch from'
- optional :author_email, type: String, desc: 'Author email for commit'
- optional :author_name, type: String, desc: 'Author name for commit'
+ optional :start_project,
+ types: [Integer, String],
+ desc: 'The ID or path of the project to start the new branch from',
+ documentation: { example: 1 }
+ optional :author_email,
+ type: String,
+ desc: 'Author email for commit',
+ documentation: { example: 'janedoe@example.com' }
+ optional :author_name,
+ type: String,
+ desc: 'Author name for commit',
+ documentation: { example: 'Jane Doe' }
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`'
end
@@ -151,8 +216,11 @@ module API
end
desc 'Get a specific commit of a project' do
- success Entities::CommitDetail
- failure [[404, 'Commit Not Found']]
+ success code: 200, model: Entities::CommitDetail
+ tags %w[commits]
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
@@ -167,7 +235,12 @@ module API
end
desc 'Get the diff for a specific commit of a project' do
- failure [[404, 'Commit Not Found']]
+ success code: 200, model: Entities::Diff
+ tags %w[commits]
+ is_array true
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
@@ -184,8 +257,12 @@ module API
end
desc "Get a commit's comments" do
- success Entities::CommitNote
- failure [[404, 'Commit Not Found']]
+ success code: 200, model: Entities::CommitNote
+ tags %w[commits]
+ is_array true
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
use :pagination
@@ -202,13 +279,25 @@ module API
desc 'Cherry pick commit into a branch' do
detail 'This feature was introduced in GitLab 8.15'
- success Entities::Commit
+ success code: 200, model: Entities::Commit
+ tags %w[commits]
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag to be cherry picked'
- requires :branch, type: String, desc: 'The name of the branch', allow_blank: false
+ requires :branch,
+ type: String,
+ desc: 'The name of the branch',
+ allow_blank: false,
+ documentation: { example: 'master' }
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
- optional :message, type: String, desc: 'A custom commit message to use for the picked commit'
+ optional :message,
+ type: String,
+ desc: 'A custom commit message to use for the picked commit',
+ documentation: { example: 'Initial commit' }
end
post ':id/repository/commits/:sha/cherry_pick', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
authorize_push_to_branch!(params[:branch])
@@ -248,11 +337,20 @@ module API
desc 'Revert a commit in a branch' do
detail 'This feature was introduced in GitLab 11.5'
- success Entities::Commit
+ success code: 200, model: Entities::Commit
+ tags %w[commits]
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'Commit SHA to revert'
- requires :branch, type: String, desc: 'Target branch name', allow_blank: false
+ requires :branch,
+ type: String,
+ desc: 'Target branch name',
+ allow_blank: false,
+ documentation: { example: 'master' }
optional :dry_run, type: Boolean, default: false, desc: "Does not commit any changes"
end
post ':id/repository/commits/:sha/revert', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
@@ -292,7 +390,12 @@ module API
desc 'Get all references a commit is pushed to' do
detail 'This feature was introduced in GitLab 10.6'
- success Entities::BasicRef
+ success code: 200, model: Entities::BasicRef
+ tags %w[commits]
+ is_array true
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha'
@@ -312,14 +415,28 @@ module API
end
desc 'Post comment to commit' do
- success Entities::CommitNote
+ success code: 200, model: Entities::CommitNote
+ tags %w[commits]
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to post a comment'
- requires :note, type: String, desc: 'The text of the comment'
- optional :path, type: String, desc: 'The file path'
+ requires :note,
+ type: String,
+ desc: 'The text of the comment',
+ documentation: { example: 'Nice code!' }
+ optional :path,
+ type: String,
+ desc: 'The file path',
+ documentation: { example: 'doc/update/5.4-to-6.0.md' }
given :path do
- requires :line, type: Integer, desc: 'The line number'
+ requires :line,
+ type: Integer,
+ desc: 'The line number',
+ documentation: { example: 11 }
requires :line_type, type: String, values: %w[new old], default: 'new', desc: 'The type of the line'
end
end
@@ -361,7 +478,12 @@ module API
end
desc 'Get Merge Requests associated with a commit' do
- success Entities::MergeRequestBasic
+ success code: 200, model: Entities::MergeRequestBasic
+ tags %w[commits]
+ is_array true
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag on which to find Merge Requests'
@@ -383,7 +505,11 @@ module API
end
desc "Get a commit's signature" do
- success Entities::CommitSignature
+ success code: 200, model: Entities::CommitSignature
+ tags %w[commits]
+ failure [
+ { code: 404, message: 'Not found' }
+ ]
end
params do
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
diff --git a/lib/api/entities/basic_ref.rb b/lib/api/entities/basic_ref.rb
index 79c15075d99..1b821a5b0ec 100644
--- a/lib/api/entities/basic_ref.rb
+++ b/lib/api/entities/basic_ref.rb
@@ -3,7 +3,8 @@
module API
module Entities
class BasicRef < Grape::Entity
- expose :type, :name
+ expose :type, documentation: { type: 'string', example: 'tag' }
+ expose :name, documentation: { type: 'string', example: 'v1.1.0' }
end
end
end
diff --git a/lib/api/entities/ci/lint/result.rb b/lib/api/entities/ci/lint/result.rb
index b44a6e13463..698b02d3b4a 100644
--- a/lib/api/entities/ci/lint/result.rb
+++ b/lib/api/entities/ci/lint/result.rb
@@ -5,12 +5,17 @@ module API
module Ci
module Lint
class Result < Grape::Entity
- expose :valid?, as: :valid
- expose :errors
- expose :warnings
- expose :merged_yaml
- expose :includes
- expose :jobs, if: -> (result, options) { options[:include_jobs] }
+ expose :valid?, as: :valid, documentation: { type: 'boolean' }
+ expose :errors, documentation: { is_array: true, type: 'string',
+ example: 'variables config should be a hash of key value pairs' }
+ expose :warnings, documentation: { is_array: true, type: 'string',
+ example: 'jobs:job may allow multiple pipelines ...' }
+ expose :merged_yaml, documentation: { type: 'string', example: '---\n:another_test:\n :stage: test\n
+ :script: echo 2\n:test:\n :stage: test\n :script: echo 1\n' }
+ expose :includes, documentation: { is_array: true, type: 'object',
+ example: '{ "blob": "https://gitlab.com/root/example-project/-/blob/...' }
+ expose :jobs, if: -> (result, options) { options[:include_jobs] },
+ documentation: { is_array: true, type: 'object', example: '{ "name": "test: .... }' }
end
end
end
diff --git a/lib/api/entities/commit.rb b/lib/api/entities/commit.rb
index a247ea2110e..ab1f51289d7 100644
--- a/lib/api/entities/commit.rb
+++ b/lib/api/entities/commit.rb
@@ -21,7 +21,7 @@ module API
expose :web_url,
documentation: {
type: 'string',
- example: 'https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746'
+ example: 'https://gitlab.example.com/janedoe/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746'
} do |commit, _options|
c = commit
c = c.__subject__ if c.is_a?(Gitlab::View::Presenter::Base)
diff --git a/lib/api/entities/commit_note.rb b/lib/api/entities/commit_note.rb
index fe91712b48d..0632dc467b8 100644
--- a/lib/api/entities/commit_note.rb
+++ b/lib/api/entities/commit_note.rb
@@ -3,12 +3,22 @@
module API
module Entities
class CommitNote < Grape::Entity
- expose :note
- expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? }
- expose(:line) { |note| note.diff_line.try(:line) if note.diff_note? }
- expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? }
+ expose :note, documentation: { type: 'string', example: 'this doc is really nice' }
+
+ expose :path, documentation: { type: 'string', example: 'README.md' } do |note|
+ note.diff_file.try(:file_path) if note.diff_note?
+ end
+
+ expose :line, documentation: { type: 'integer', example: 11 } do |note|
+ note.diff_line.try(:line) if note.diff_note?
+ end
+
+ expose :line_type, documentation: { type: 'string', example: 'new' } do |note|
+ note.diff_line.try(:type) if note.diff_note?
+ end
+
expose :author, using: Entities::UserBasic
- expose :created_at
+ expose :created_at, documentation: { type: 'dateTime', example: '2016-01-19T09:44:55.600Z' }
end
end
end
diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb
index 0d8e977a9f5..9430dd5e2a2 100644
--- a/lib/api/entities/commit_signature.rb
+++ b/lib/api/entities/commit_signature.rb
@@ -3,7 +3,7 @@
module API
module Entities
class CommitSignature < Grape::Entity
- expose :signature_type
+ expose :signature_type, documentation: { type: 'string', example: 'PGP' }
expose :signature, merge: true do |commit, options|
if commit.signature.is_a?(::CommitSignatures::GpgSignature) || commit.raw_commit_from_rugged?
@@ -13,7 +13,7 @@ module API
end
end
- expose :commit_source do |commit, _|
+ expose :commit_source, documentation: { type: 'string', example: 'gitaly' } do |commit, _|
commit.raw_commit_from_rugged? ? "rugged" : "gitaly"
end
diff --git a/lib/api/entities/compare.rb b/lib/api/entities/compare.rb
index 75a36d9bb01..92066868d3c 100644
--- a/lib/api/entities/compare.rb
+++ b/lib/api/entities/compare.rb
@@ -7,21 +7,24 @@ module API
compare.commits.last
end
- expose :commits, using: Entities::Commit do |compare, _|
+ expose :commits, documentation: { is_array: true }, using: Entities::Commit do |compare, _|
compare.commits
end
- expose :diffs, using: Entities::Diff do |compare, _|
+ expose :diffs, documentation: { is_array: true }, using: Entities::Diff do |compare, _|
compare.diffs.diffs.to_a
end
- expose :compare_timeout do |compare, _|
+ expose :compare_timeout, documentation: { type: 'boolean' } do |compare, _|
compare.diffs.diffs.overflow?
end
- expose :same, as: :compare_same_ref
+ expose :same, as: :compare_same_ref, documentation: { type: 'boolean' }
- expose :web_url do |compare, _|
+ expose :web_url,
+ documentation: {
+ example: "https://gitlab.example.com/gitlab/gitlab-foss/-/compare/main...feature"
+ } do |compare, _|
Gitlab::UrlBuilder.build(compare)
end
end
diff --git a/lib/api/entities/contributor.rb b/lib/api/entities/contributor.rb
index 8763822b674..4fab953f0f6 100644
--- a/lib/api/entities/contributor.rb
+++ b/lib/api/entities/contributor.rb
@@ -3,7 +3,11 @@
module API
module Entities
class Contributor < Grape::Entity
- expose :name, :email, :commits, :additions, :deletions
+ expose :name, documentation: { example: 'John Doe' }
+ expose :email, documentation: { example: 'johndoe@example.com' }
+ expose :commits, documentation: { type: 'integer', example: 117 }
+ expose :additions, documentation: { type: 'integer', example: 3 }
+ expose :deletions, documentation: { type: 'integer', example: 5 }
end
end
end
diff --git a/lib/api/entities/diff.rb b/lib/api/entities/diff.rb
index e92bc5d6b68..e9650f07f00 100644
--- a/lib/api/entities/diff.rb
+++ b/lib/api/entities/diff.rb
@@ -3,11 +3,17 @@
module API
module Entities
class Diff < Grape::Entity
- expose :old_path, :new_path, :a_mode, :b_mode
- expose :new_file?, as: :new_file
- expose :renamed_file?, as: :renamed_file
- expose :deleted_file?, as: :deleted_file
- expose :json_safe_diff, as: :diff
+ expose :json_safe_diff, as: :diff, documentation: {
+ type: 'string',
+ example: '--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n...'
+ }
+ expose :new_path, documentation: { type: 'string', example: 'doc/update/5.4-to-6.0.md' }
+ expose :old_path, documentation: { type: 'string', example: 'doc/update/5.4-to-6.0.md' }
+ expose :a_mode, documentation: { type: 'string', example: '100755' }
+ expose :b_mode, documentation: { type: 'string', example: '100644' }
+ expose :new_file?, as: :new_file, documentation: { type: 'boolean' }
+ expose :renamed_file?, as: :renamed_file, documentation: { type: 'boolean' }
+ expose :deleted_file?, as: :deleted_file, documentation: { type: 'boolean' }
end
end
end
diff --git a/lib/api/entities/tree_object.rb b/lib/api/entities/tree_object.rb
index e4e840ebe43..1f542885169 100644
--- a/lib/api/entities/tree_object.rb
+++ b/lib/api/entities/tree_object.rb
@@ -3,9 +3,12 @@
module API
module Entities
class TreeObject < Grape::Entity
- expose :id, :name, :type, :path
+ expose :id, documentation: { example: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba' }
+ expose :name, documentation: { example: 'html' }
+ expose :type, documentation: { example: 'tree' }
+ expose :path, documentation: { example: 'files/html' }
- expose :mode do |obj, options|
+ expose :mode, documentation: { example: '040000' } do |obj, options|
filemode = obj.mode
filemode = "0" + filemode if filemode.length < 6
filemode
diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb
index 9e21c5e12b6..eda72d2cfc6 100644
--- a/lib/api/entities/user_public.rb
+++ b/lib/api/entities/user_public.rb
@@ -17,7 +17,7 @@ module API
expose :two_factor_enabled?, as: :two_factor_enabled, documentation: { type: 'boolean', example: true }
- expose :external, documentation: { type: 'boolean', example: false }
+ expose :external
expose :private_profile, documentation: { type: 'boolean', example: :null }
expose :commit_email_or_default, as: :commit_email
diff --git a/lib/api/entities/x509_certificate.rb b/lib/api/entities/x509_certificate.rb
index aad11339148..95d4948906e 100644
--- a/lib/api/entities/x509_certificate.rb
+++ b/lib/api/entities/x509_certificate.rb
@@ -3,13 +3,16 @@
module API
module Entities
class X509Certificate < Grape::Entity
- expose :id
- expose :subject
- expose :subject_key_identifier
- expose :email
- expose :serial_number
- expose :certificate_status
- expose :x509_issuer, using: 'API::Entities::X509Issuer'
+ expose :id, documentation: { type: 'integer', example: 1 }
+ expose :subject, documentation: { type: 'string', example: 'CN=gitlab@example.org,OU=Example,O=World' }
+ expose :subject_key_identifier, documentation: {
+ type: 'string',
+ example: 'BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC'
+ }
+ expose :email, documentation: { type: 'string', example: 'gitlab@example.org' }
+ expose :serial_number, documentation: { type: 'integer', example: 278969561018901340486471282831158785578 }
+ expose :certificate_status, documentation: { type: 'string', example: 'good' }
+ expose :x509_issuer, using: 'API::Entities::X509Issuer', documentation: { type: 'string', example: '100755' }
end
end
end
diff --git a/lib/api/entities/x509_issuer.rb b/lib/api/entities/x509_issuer.rb
index b480bc107bc..22429560c72 100644
--- a/lib/api/entities/x509_issuer.rb
+++ b/lib/api/entities/x509_issuer.rb
@@ -3,10 +3,13 @@
module API
module Entities
class X509Issuer < Grape::Entity
- expose :id
- expose :subject
- expose :subject_key_identifier
- expose :crl_url
+ expose :id, documentation: { type: 'integer', example: 1 }
+ expose :subject, documentation: { type: 'string', example: 'CN=PKI,OU=Example,O=World' }
+ expose :subject_key_identifier, documentation: {
+ type: 'string',
+ example: 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB'
+ }
+ expose :crl_url, documentation: { type: 'string', example: 'http://example.com/pki.crl' }
end
end
end
diff --git a/lib/api/entities/x509_signature.rb b/lib/api/entities/x509_signature.rb
index 909b630288c..c3f0cb3659d 100644
--- a/lib/api/entities/x509_signature.rb
+++ b/lib/api/entities/x509_signature.rb
@@ -3,7 +3,7 @@
module API
module Entities
class X509Signature < Grape::Entity
- expose :verification_status
+ expose :verification_status, documentation: { type: 'string', example: 'unverified' }
expose :x509_certificate, using: 'API::Entities::X509Certificate'
end
end
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
index 2948960a9b4..eb0a01e0d3d 100644
--- a/lib/api/group_export.rb
+++ b/lib/api/group_export.rb
@@ -15,6 +15,16 @@ module API
resource :groups, requirements: { id: %r{[^/]+} } do
desc 'Download export' do
detail 'This feature was introduced in GitLab 12.5.'
+ tags %w[group_export]
+ produces %w[application/octet-stream application/json]
+ success code: 200
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 503, message: 'Service unavailable' }
+ ]
end
get ':id/export/download' do
check_rate_limit! :group_download_export, scope: [current_user, user_group]
@@ -32,6 +42,15 @@ module API
desc 'Start export' do
detail 'This feature was introduced in GitLab 12.5.'
+ tags %w[group_export]
+ success code: 202
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 429, message: 'Too many requests' },
+ { code: 503, message: 'Service unavailable' }
+ ]
end
post ':id/export' do
check_rate_limit! :group_export, scope: current_user
@@ -47,6 +66,14 @@ module API
desc 'Start relations export' do
detail 'This feature was introduced in GitLab 13.12'
+ tags %w[group_export]
+ success code: 202
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 503, message: 'Service unavailable' }
+ ]
end
post ':id/export_relations' do
response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute
@@ -60,6 +87,15 @@ module API
desc 'Download relations export' do
detail 'This feature was introduced in GitLab 13.12'
+ produces %w[application/octet-stream application/json]
+ tags %w[group_export]
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 503, message: 'Service unavailable' }
+ ]
end
params do
requires :relation, type: String, desc: 'Group relation name'
@@ -77,6 +113,15 @@ module API
desc 'Relations export status' do
detail 'This feature was introduced in GitLab 13.12'
+ is_array true
+ tags %w[group_export]
+ success code: 200, model: Entities::BulkImports::ExportStatus
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 503, message: 'Service unavailable' }
+ ]
end
get ':id/export_relations/status' do
present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus
diff --git a/lib/api/lint.rb b/lib/api/lint.rb
index f65ecf3b4a6..1d19d653d8b 100644
--- a/lib/api/lint.rb
+++ b/lib/api/lint.rb
@@ -15,12 +15,18 @@ module API
end
namespace :ci do
- desc 'Validation of .gitlab-ci.yml content'
+ desc 'Validates the .gitlab-ci.yml content' do
+ detail 'Checks if CI/CD YAML configuration is valid'
+ success code: 200, model: Entities::Ci::Lint::Result
+ tags %w[ci_lint]
+ end
params do
- requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
- optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
- optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
+ requires :content, type: String, desc: 'The CI/CD configuration content'
+ optional :include_merged_yaml, type: Boolean, desc: 'If the expanded CI/CD configuration should be included in the response'
+ optional :include_jobs, type: Boolean, desc: 'If the list of jobs should be included in the response. This is
+ false by default'
end
+
post '/lint', urgency: :low do
unauthorized! unless can_lint_ci?
@@ -36,14 +42,19 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Validation of .gitlab-ci.yml content' do
- detail 'This feature was introduced in GitLab 13.5.'
+ desc 'Validates a CI YAML configuration with a namespace' do
+ detail 'Checks if a project’s latest (HEAD of the project’s default branch) .gitlab-ci.yml configuration is
+ valid'
+ success Entities::Ci::Lint::Result
+ tags %w[ci_lint]
end
params do
- optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
- optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
+ optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check. This is false by default'
+ optional :include_jobs, type: Boolean, desc: 'If the list of jobs that would exist in a static check or pipeline
+ simulation should be included in the response. This is false by default'
optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true'
end
+
get ':id/ci/lint', urgency: :low do
authorize! :download_code, user_project
@@ -60,15 +71,19 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Validation of .gitlab-ci.yml content' do
- detail 'This feature was introduced in GitLab 13.6.'
+ desc 'Validate a CI YAML configuration with a namespace' do
+ detail 'Checks if CI/CD YAML configuration is valid. This endpoint has namespace specific context'
+ success code: 200, model: Entities::Ci::Lint::Result
+ tags %w[ci_lint]
end
params do
requires :content, type: String, desc: 'Content of .gitlab-ci.yml'
- optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check.'
- optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response'
- optional :ref, type: String, desc: 'Branch or tag used to execute a dry run. Defaults to the default branch of the project. Only used when dry_run is true'
+ optional :dry_run, type: Boolean, default: false, desc: 'Run pipeline creation simulation, or only do static check. This is false by default'
+ optional :include_jobs, type: Boolean, desc: 'If the list of jobs that would exist in a static check or pipeline
+ simulation should be included in the response. This is false by default'
+ optional :ref, type: String, desc: 'When dry_run is true, sets the branch or tag to use. Defaults to the project’s default branch when not set'
end
+
post ':id/ci/lint', urgency: :low do
authorize! :create_pipeline, user_project
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 640b23b402b..beba2842316 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -15,24 +15,29 @@ module API
requires :version,
type: String,
regexp: Gitlab::Regex.unbounded_semver_regex,
- desc: 'The version of the release, using the semantic versioning format'
+ desc: 'The version of the release, using the semantic versioning format',
+ documentation: { example: '1.0.0' }
optional :from,
type: String,
- desc: 'The first commit in the range of commits to use for the changelog'
+ desc: 'The first commit in the range of commits to use for the changelog',
+ documentation: { example: 'ed899a2f4b50b4370feeea94676502b42383c746' }
optional :to,
type: String,
- desc: 'The last commit in the range of commits to use for the changelog'
+ desc: 'The last commit in the range of commits to use for the changelog',
+ documentation: { example: '6104942438c14ec7bd21c6cd5bd995272b3faff6' }
optional :date,
type: DateTime,
- desc: 'The date and time of the release'
+ desc: 'The date and time of the release',
+ documentation: { type: 'dateTime', example: '2021-09-20T11:50:22.001+00:00' }
optional :trailer,
type: String,
desc: 'The Git trailer to use for determining if commits are to be included in the changelog',
- default: ::Repositories::ChangelogService::DEFAULT_TRAILER
+ default: ::Repositories::ChangelogService::DEFAULT_TRAILER,
+ documentation: { example: 'Changelog' }
end
end
@@ -41,7 +46,9 @@ module API
feature_category :source_code_management
params do
- requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
+ requires :id, types: [String, Integer],
+ desc: 'The ID or URL-encoded path of the project',
+ documentation: { example: 1 }
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
helpers do
@@ -94,15 +101,19 @@ module API
success Entities::TreeObject
end
params do
- optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used'
- optional :path, type: String, desc: 'The path of the tree'
+ optional :ref, type: String,
+ desc: 'The name of a repository branch or tag, if not given the default branch is used',
+ documentation: { example: 'main' }
+ optional :path, type: String, desc: 'The path of the tree', documentation: { example: 'files/html' }
optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree'
use :pagination
optional :pagination, type: String, values: %w(legacy keyset none), default: 'legacy', desc: 'Specify the pagination method ("none" is only valid if "recursive" is true)'
given pagination: ->(value) { value == 'keyset' } do
- optional :page_token, type: String, desc: 'Record from which to start the keyset pagination'
+ optional :page_token, type: String,
+ desc: 'Record from which to start the keyset pagination',
+ documentation: { example: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba' }
end
given pagination: ->(value) { value == 'none' } do
@@ -123,7 +134,8 @@ module API
desc 'Get raw blob contents from the repository'
params do
- requires :sha, type: String, desc: 'The commit hash'
+ requires :sha, type: String,
+ desc: 'The commit hash', documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' }
end
get ':id/repository/blobs/:sha/raw' do
# Load metadata enough to ask Workhorse to load the whole blob
@@ -136,7 +148,8 @@ module API
desc 'Get a blob from the repository'
params do
- requires :sha, type: String, desc: 'The commit hash'
+ requires :sha, type: String,
+ desc: 'The commit hash', documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' }
end
get ':id/repository/blobs/:sha' do
assign_blob_vars!(limit: -1)
@@ -151,9 +164,12 @@ module API
desc 'Get an archive of the repository'
params do
- optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
- optional :format, type: String, desc: 'The archive format'
- optional :path, type: String, desc: 'Subfolder of the repository to be downloaded'
+ optional :sha, type: String,
+ desc: 'The commit sha of the archive to be downloaded',
+ documentation: { example: '7d70e02340bac451f281cecf0a980907974bd8be' }
+ optional :format, type: String, desc: 'The archive format', documentation: { example: 'tar.gz' }
+ optional :path, type: String,
+ desc: 'Subfolder of the repository to be downloaded', documentation: { example: 'files/archives' }
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
check_archive_rate_limit!(current_user, user_project) do
@@ -171,9 +187,13 @@ module API
success Entities::Compare
end
params do
- requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison'
- requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison'
- optional :from_project_id, type: Integer, desc: 'The project to compare from'
+ requires :from, type: String,
+ desc: 'The commit, branch name, or tag name to start comparison',
+ documentation: { example: 'main' }
+ requires :to, type: String,
+ desc: 'The commit, branch name, or tag name to stop comparison',
+ documentation: { example: 'feature' }
+ optional :from_project_id, type: Integer, desc: 'The project to compare from', documentation: { example: 1 }
optional :straight, type: Boolean, desc: 'Comparison method, `true` for direct comparison between `from` and `to` (`from`..`to`), `false` to compare using merge base (`from`...`to`)', default: false
end
get ':id/repository/compare', urgency: :low do
@@ -215,7 +235,10 @@ module API
success Entities::Commit
end
params do
- requires :refs, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The refs to find the common ancestor of, multiple refs can be passed'
+ requires :refs, type: Array[String],
+ coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce,
+ desc: 'The refs to find the common ancestor of, multiple refs can be passed',
+ documentation: { example: 'main' }
end
get ':id/repository/merge_base' do
refs = params[:refs]
@@ -241,12 +264,14 @@ module API
desc 'Generates a changelog section for a release and returns it' do
detail 'This feature was introduced in GitLab 14.6'
+ success Entities::Changelog
end
params do
use :release_params
optional :config_file,
type: String,
+ documentation: { example: '.gitlab/changelog_config.yml' },
desc: "The file path to the configuration file as stored in the project's Git repository. Defaults to '.gitlab/changelog_config.yml'"
end
get ':id/repository/changelog' do
@@ -264,26 +289,31 @@ module API
desc 'Generates a changelog section for a release and commits it in a changelog file' do
detail 'This feature was introduced in GitLab 13.9'
+ success code: 200
end
params do
use :release_params
optional :branch,
type: String,
- desc: 'The branch to commit the changelog changes to'
+ desc: 'The branch to commit the changelog changes to',
+ documentation: { example: 'main' }
optional :config_file,
type: String,
+ documentation: { example: '.gitlab/changelog_config.yml' },
desc: "The file path to the configuration file as stored in the project's Git repository. Defaults to '.gitlab/changelog_config.yml'"
optional :file,
type: String,
desc: 'The file to commit the changelog changes to',
- default: ::Repositories::ChangelogService::DEFAULT_FILE
+ default: ::Repositories::ChangelogService::DEFAULT_FILE,
+ documentation: { example: 'CHANGELOG.md' }
optional :message,
type: String,
- desc: 'The commit message to use when committing the changelog'
+ desc: 'The commit message to use when committing the changelog',
+ documentation: { example: 'Initial commit' }
end
post ':id/repository/changelog' do
branch = params[:branch] || user_project.default_branch_or_main
diff --git a/spec/controllers/groups/settings/repository_controller_spec.rb b/spec/controllers/groups/settings/repository_controller_spec.rb
index cbf55218b94..73a205069f5 100644
--- a/spec/controllers/groups/settings/repository_controller_spec.rb
+++ b/spec/controllers/groups/settings/repository_controller_spec.rb
@@ -13,88 +13,73 @@ RSpec.describe Groups::Settings::RepositoryController do
end
describe 'POST create_deploy_token' do
- context 'when ajax_new_deploy_token feature flag is disabled for the project' do
- before do
- stub_feature_flags(ajax_new_deploy_token: false)
- entity.add_owner(user)
- end
+ let(:good_deploy_token_params) do
+ {
+ name: 'name',
+ expires_at: 1.day.from_now.to_s,
+ username: 'deployer',
+ read_repository: '1',
+ deploy_token_type: DeployToken.deploy_token_types[:group_type]
+ }
+ end
- it_behaves_like 'a created deploy token' do
- let(:entity) { group }
- let(:create_entity_params) { { group_id: group } }
- let(:deploy_token_type) { DeployToken.deploy_token_types[:group_type] }
- end
+ let(:request_params) do
+ {
+ group_id: group.to_param,
+ deploy_token: deploy_token_params
+ }
end
- context 'when ajax_new_deploy_token feature flag is enabled for the project' do
- let(:good_deploy_token_params) do
- {
- name: 'name',
- expires_at: 1.day.from_now.to_s,
- username: 'deployer',
- read_repository: '1',
- deploy_token_type: DeployToken.deploy_token_types[:group_type]
- }
- end
+ before do
+ group.add_owner(user)
+ end
+
+ subject { post :create_deploy_token, params: request_params, format: :json }
- let(:request_params) do
+ context('a good request') do
+ let(:deploy_token_params) { good_deploy_token_params }
+ let(:expected_response) do
{
- group_id: group.to_param,
- deploy_token: deploy_token_params
+ 'id' => be_a(Integer),
+ 'name' => deploy_token_params[:name],
+ 'username' => deploy_token_params[:username],
+ 'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
+ 'token' => be_a(String),
+ 'expired' => false,
+ 'revoked' => false,
+ 'scopes' => deploy_token_params.inject([]) do |scopes, kv|
+ key, value = kv
+ key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
+ end
}
end
- before do
- group.add_owner(user)
- end
+ it 'creates the deploy token' do
+ subject
- subject { post :create_deploy_token, params: request_params, format: :json }
-
- context('a good request') do
- let(:deploy_token_params) { good_deploy_token_params }
- let(:expected_response) do
- {
- 'id' => be_a(Integer),
- 'name' => deploy_token_params[:name],
- 'username' => deploy_token_params[:username],
- 'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
- 'token' => be_a(String),
- 'expired' => false,
- 'revoked' => false,
- 'scopes' => deploy_token_params.inject([]) do |scopes, kv|
- key, value = kv
- key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
- end
- }
- end
-
- it 'creates the deploy token' do
- subject
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('public_api/v4/deploy_token')
- expect(json_response).to match(expected_response)
- end
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/deploy_token')
+ expect(json_response).to match(expected_response)
end
+ end
- context('a bad request') do
- let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
- let(:expected_response) { { 'message' => "Scopes can't be blank" } }
+ context('a bad request') do
+ let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
+ let(:expected_response) { { 'message' => "Scopes can't be blank" } }
- it 'does not create the deploy token' do
- subject
+ it 'does not create the deploy token' do
+ subject
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to match(expected_response)
- end
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to match(expected_response)
end
+ end
- context('an invalid request') do
- let(:deploy_token_params) { good_deploy_token_params.except(:name) }
+ context('an invalid request') do
+ let(:deploy_token_params) { good_deploy_token_params.except(:name) }
- it 'raises a validation error' do
- expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
- end
+ it 'raises a validation error' do
+ expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb
index 2d145b6ff1d..81c1d4acd36 100644
--- a/spec/controllers/projects/artifacts_controller_spec.rb
+++ b/spec/controllers/projects/artifacts_controller_spec.rb
@@ -30,28 +30,10 @@ RSpec.describe Projects::ArtifactsController do
stub_feature_flags(artifacts_management_page: true)
end
- it 'sets the artifacts variable' do
+ it 'renders the page' do
subject
- expect(assigns(:artifacts)).to contain_exactly(*project.job_artifacts)
- end
-
- it 'sets the total size variable' do
- subject
-
- expect(assigns(:total_size)).to eq(project.job_artifacts.total_size)
- end
-
- describe 'pagination' do
- before do
- stub_const("#{described_class}::MAX_PER_PAGE", 1)
- end
-
- it 'paginates artifacts' do
- subject
-
- expect(assigns(:artifacts)).to contain_exactly(project.reload.job_artifacts.last)
- end
+ expect(response).to have_gitlab_http_status(:ok)
end
end
@@ -65,18 +47,6 @@ RSpec.describe Projects::ArtifactsController do
expect(response).to have_gitlab_http_status(:no_content)
end
-
- it 'does not set the artifacts variable' do
- subject
-
- expect(assigns(:artifacts)).to eq(nil)
- end
-
- it 'does not set the total size variable' do
- subject
-
- expect(assigns(:total_size)).to eq(nil)
- end
end
end
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb
index 6e04e3991ab..ea50ff6caa0 100644
--- a/spec/controllers/projects/settings/repository_controller_spec.rb
+++ b/spec/controllers/projects/settings/repository_controller_spec.rb
@@ -51,78 +51,64 @@ RSpec.describe Projects::Settings::RepositoryController do
end
describe 'POST create_deploy_token' do
- context 'when ajax_new_deploy_token feature flag is disabled for the project' do
- before do
- stub_feature_flags(ajax_new_deploy_token: false)
- end
-
- it_behaves_like 'a created deploy token' do
- let(:entity) { project }
- let(:create_entity_params) { base_params }
- let(:deploy_token_type) { DeployToken.deploy_token_types[:project_type] }
- end
+ let(:good_deploy_token_params) do
+ {
+ name: 'name',
+ expires_at: 1.day.from_now.to_s,
+ username: 'deployer',
+ read_repository: '1',
+ deploy_token_type: DeployToken.deploy_token_types[:project_type]
+ }
end
- context 'when ajax_new_deploy_token feature flag is enabled for the project' do
- let(:good_deploy_token_params) do
+ let(:request_params) { base_params.merge({ deploy_token: deploy_token_params }) }
+
+ subject { post :create_deploy_token, params: request_params, format: :json }
+
+ context('a good request') do
+ let(:deploy_token_params) { good_deploy_token_params }
+ let(:expected_response) do
{
- name: 'name',
- expires_at: 1.day.from_now.to_s,
- username: 'deployer',
- read_repository: '1',
- deploy_token_type: DeployToken.deploy_token_types[:project_type]
+ 'id' => be_a(Integer),
+ 'name' => deploy_token_params[:name],
+ 'username' => deploy_token_params[:username],
+ 'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
+ 'token' => be_a(String),
+ 'expired' => false,
+ 'revoked' => false,
+ 'scopes' => deploy_token_params.inject([]) do |scopes, kv|
+ key, value = kv
+ key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
+ end
}
end
- let(:request_params) { base_params.merge({ deploy_token: deploy_token_params }) }
-
- subject { post :create_deploy_token, params: request_params, format: :json }
-
- context('a good request') do
- let(:deploy_token_params) { good_deploy_token_params }
- let(:expected_response) do
- {
- 'id' => be_a(Integer),
- 'name' => deploy_token_params[:name],
- 'username' => deploy_token_params[:username],
- 'expires_at' => Time.zone.parse(deploy_token_params[:expires_at]),
- 'token' => be_a(String),
- 'expired' => false,
- 'revoked' => false,
- 'scopes' => deploy_token_params.inject([]) do |scopes, kv|
- key, value = kv
- key.to_s.start_with?('read_') && value.to_i != 0 ? scopes << key.to_s : scopes
- end
- }
- end
+ it 'creates the deploy token' do
+ subject
- it 'creates the deploy token' do
- subject
-
- expect(response).to have_gitlab_http_status(:created)
- expect(response).to match_response_schema('public_api/v4/deploy_token')
- expect(json_response).to match(expected_response)
- end
+ expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/deploy_token')
+ expect(json_response).to match(expected_response)
end
+ end
- context('a bad request') do
- let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
- let(:expected_response) { { 'message' => "Scopes can't be blank" } }
+ context('a bad request') do
+ let(:deploy_token_params) { good_deploy_token_params.except(:read_repository) }
+ let(:expected_response) { { 'message' => "Scopes can't be blank" } }
- it 'does not create the deploy token' do
- subject
+ it 'does not create the deploy token' do
+ subject
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response).to match(expected_response)
- end
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response).to match(expected_response)
end
+ end
- context('an invalid request') do
- let(:deploy_token_params) { good_deploy_token_params.except(:name) }
+ context('an invalid request') do
+ let(:deploy_token_params) { good_deploy_token_params.except(:name) }
- it 'raises a validation error' do
- expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
- end
+ it 'raises a validation error' do
+ expect { subject }.to raise_error(ActiveRecord::StatementInvalid)
end
end
end
diff --git a/spec/features/groups/settings/repository_spec.rb b/spec/features/groups/settings/repository_spec.rb
index f6b8bbdd35f..cd7dcbdb28d 100644
--- a/spec/features/groups/settings/repository_spec.rb
+++ b/spec/features/groups/settings/repository_spec.rb
@@ -23,26 +23,9 @@ RSpec.describe 'Group Repository settings', :js do
stub_container_registry_config(enabled: true)
end
- context 'when ajax deploy tokens is enabled' do
- before do
- stub_feature_flags(ajax_new_deploy_token: true)
- end
-
- it_behaves_like 'a deploy token in settings' do
- let(:entity_type) { 'group' }
- let(:page_path) { group_settings_repository_path(group) }
- end
- end
-
- context 'when ajax deploy tokens is disabled' do
- before do
- stub_feature_flags(ajax_new_deploy_token: false)
- end
-
- it_behaves_like 'a deploy token in settings' do
- let(:entity_type) { 'group' }
- let(:page_path) { group_settings_repository_path(group) }
- end
+ it_behaves_like 'a deploy token in settings' do
+ let(:entity_type) { 'group' }
+ let(:page_path) { group_settings_repository_path(group) }
end
end
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index d9bdbf7aa1a..b25ae80b3c3 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -30,7 +30,6 @@ RSpec.describe 'Projects > Settings > Repository settings' do
before do
stub_container_registry_config(enabled: true)
- stub_feature_flags(ajax_new_deploy_token: project)
end
it_behaves_like 'a deploy token in settings' do
diff --git a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
index eed3494ef5b..47383be1ba1 100644
--- a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
+++ b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
@@ -11,7 +11,6 @@ RSpec.describe 'Repository Settings > User sees revoke deploy token modal', :js
before do
project.add_role(user, role)
sign_in(user)
- stub_feature_flags(ajax_new_deploy_token: project)
visit(project_settings_repository_path(project))
click_button('Revoke')
end
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 3ebc51c4bcb..3b77c999a9e 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -7,7 +7,6 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import IssuableBlockedIcon from '~/vue_shared/components/issuable_blocked_icon/issuable_blocked_icon.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
-import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { issuableTypes } from '~/boards/constants';
import eventHub from '~/boards/eventhub';
@@ -49,7 +48,6 @@ describe('Board card component', () => {
const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight');
const findEpicProgressTooltip = () => wrapper.findByTestId('epic-progress-tooltip-content');
const findHiddenIssueIcon = () => wrapper.findByTestId('hidden-icon');
- const findMoveToPositionComponent = () => wrapper.findComponent(BoardCardMoveToPosition);
const findWorkItemIcon = () => wrapper.findComponent(WorkItemTypeIcon);
const performSearchMock = jest.fn();
@@ -143,10 +141,6 @@ describe('Board card component', () => {
expect(findHiddenIssueIcon().exists()).toBe(false);
});
- it('renders the move to position icon', () => {
- expect(findMoveToPositionComponent().exists()).toBe(true);
- });
-
it('does not render the work type icon by default', () => {
expect(findWorkItemIcon().exists()).toBe(false);
});
@@ -595,10 +589,5 @@ describe('Board card component', () => {
expect(findEpicCountablesTotalWeight().text()).toBe('15');
expect(findEpicProgressTooltip().text()).toBe('10 of 15 weight completed');
});
-
- it('does not render the move to position icon', () => {
- createWrapper();
- expect(findMoveToPositionComponent().exists()).toBe(false);
- });
});
});
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 9b0c0b93ffb..3a2beb714e9 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import createComponent from 'jest/boards/board_list_helper';
import BoardCard from '~/boards/components/board_card.vue';
import eventHub from '~/boards/eventhub';
+import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue';
import { mockIssues } from './mock_data';
@@ -15,6 +16,7 @@ describe('Board list component', () => {
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findIssueCountLoadingIcon = () => wrapper.find('[data-testid="count-loading-icon"]');
const findDraggable = () => wrapper.findComponent(Draggable);
+ const findMoveToPositionComponent = () => wrapper.findComponent(BoardCardMoveToPosition);
const startDrag = (
params = {
@@ -99,6 +101,10 @@ describe('Board list component', () => {
await nextTick();
expect(wrapper.find('.board-list-count').attributes('data-issue-id')).toBe('-1');
});
+
+ it('renders the move to position icon', () => {
+ expect(findMoveToPositionComponent().exists()).toBe(true);
+ });
});
describe('load more issues', () => {
diff --git a/spec/frontend/boards/components/board_card_move_to_position_spec.js b/spec/frontend/boards/components/board_card_move_to_position_spec.js
index 7254b9486ef..8dee3c77787 100644
--- a/spec/frontend/boards/components/board_card_move_to_position_spec.js
+++ b/spec/frontend/boards/components/board_card_move_to_position_spec.js
@@ -48,6 +48,7 @@ describe('Board Card Move to position', () => {
propsData: {
item: mockIssue2,
list: mockList,
+ listItemsLength: 3,
index: 0,
...propsData,
},
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index 0542e96c77c..fa5ccfeb478 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -107,3 +107,87 @@ export const PROMISE_ALL_EXPECTED_MUTATIONS = {
payload: { key: PROJECTS_LOCAL_STORAGE_KEY, data: [MOCK_FRESH_DATA_RES, MOCK_FRESH_DATA_RES] },
},
};
+
+export const MOCK_NAVIGATION = {
+ projects: {
+ label: 'Projects',
+ scope: 'projects',
+ link: '/search?scope=projects&search=et',
+ count_link: '/search/count?scope=projects&search=et',
+ },
+ blobs: {
+ label: 'Code',
+ scope: 'blobs',
+ link: '/search?scope=blobs&search=et',
+ count_link: '/search/count?scope=blobs&search=et',
+ },
+ issues: {
+ label: 'Issues',
+ scope: 'issues',
+ link: '/search?scope=issues&search=et',
+ active: true,
+ count: '2,430',
+ },
+ merge_requests: {
+ label: 'Merge requests',
+ scope: 'merge_requests',
+ link: '/search?scope=merge_requests&search=et',
+ count_link: '/search/count?scope=merge_requests&search=et',
+ },
+ wiki_blobs: {
+ label: 'Wiki',
+ scope: 'wiki_blobs',
+ link: '/search?scope=wiki_blobs&search=et',
+ count_link: '/search/count?scope=wiki_blobs&search=et',
+ },
+ commits: {
+ label: 'Commits',
+ scope: 'commits',
+ link: '/search?scope=commits&search=et',
+ count_link: '/search/count?scope=commits&search=et',
+ },
+ notes: {
+ label: 'Comments',
+ scope: 'notes',
+ link: '/search?scope=notes&search=et',
+ count_link: '/search/count?scope=notes&search=et',
+ },
+ milestones: {
+ label: 'Milestones',
+ scope: 'milestones',
+ link: '/search?scope=milestones&search=et',
+ count_link: '/search/count?scope=milestones&search=et',
+ },
+ users: {
+ label: 'Users',
+ scope: 'users',
+ link: '/search?scope=users&search=et',
+ count_link: '/search/count?scope=users&search=et',
+ },
+};
+
+export const MOCK_NAVIGATION_DATA = {
+ projects: {
+ label: 'Projects',
+ scope: 'projects',
+ link: '/search?scope=projects&search=et',
+ count_link: '/search/count?scope=projects&search=et',
+ },
+};
+
+export const MOCK_ENDPOINT_RESPONSE = { count: '13' };
+
+export const MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION = {
+ projects: {
+ count: '13',
+ label: 'Projects',
+ scope: 'projects',
+ link: '/search?scope=projects&search=et',
+ count_link: '/search/count?scope=projects&search=et',
+ },
+};
+
+export const MOCK_NAVIGATION_ACTION_MUTATION = {
+ type: types.RECEIVE_NAVIGATION_COUNT,
+ payload: { key: 'projects', count: '13' },
+};
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index 89959feec39..b4e3388f067 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -1,11 +1,9 @@
-import { GlButton, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import GlobalSearchSidebar from '~/search/sidebar/components/app.vue';
-import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
-import StatusFilter from '~/search/sidebar/components/status_filter.vue';
+import ResultsFilters from '~/search/sidebar/components/results_filters.vue';
Vue.use(Vuex);
@@ -35,118 +33,49 @@ describe('GlobalSearchSidebar', () => {
wrapper.destroy();
});
- const findSidebarForm = () => wrapper.find('form');
- const findStatusFilter = () => wrapper.findComponent(StatusFilter);
- const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter);
- const findApplyButton = () => wrapper.findComponent(GlButton);
- const findResetLinkButton = () => wrapper.findComponent(GlLink);
+ const findSidebarSection = () => wrapper.find('section');
+ const findFilters = () => wrapper.findComponent(ResultsFilters);
- describe('template', () => {
+ describe('renders properly', () => {
describe('scope=projects', () => {
beforeEach(() => {
createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'projects' } });
});
- it("doesn't render StatusFilter", () => {
- expect(findStatusFilter().exists()).toBe(false);
+ it('shows section', () => {
+ expect(findSidebarSection().exists()).toBe(true);
});
- it("doesn't render ConfidentialityFilter", () => {
- expect(findConfidentialityFilter().exists()).toBe(false);
- });
-
- it("doesn't render ApplyButton", () => {
- expect(findApplyButton().exists()).toBe(false);
- });
- });
-
- describe('scope=issues', () => {
- beforeEach(() => {
- createComponent({ urlQuery: MOCK_QUERY });
- });
- it('renders StatusFilter', () => {
- expect(findStatusFilter().exists()).toBe(true);
- });
-
- it('renders ConfidentialityFilter', () => {
- expect(findConfidentialityFilter().exists()).toBe(true);
- });
-
- it('renders ApplyButton', () => {
- expect(findApplyButton().exists()).toBe(true);
- });
- });
- });
-
- describe('ApplyButton', () => {
- describe('when sidebarDirty is false', () => {
- beforeEach(() => {
- createComponent({ sidebarDirty: false });
- });
-
- it('disables the button', () => {
- expect(findApplyButton().attributes('disabled')).toBe('true');
+ it("doesn't shows filters", () => {
+ expect(findFilters().exists()).toBe(false);
});
});
- describe('when sidebarDirty is true', () => {
+ describe('scope=merge_requests', () => {
beforeEach(() => {
- createComponent({ sidebarDirty: true });
+ createComponent({ urlQuery: { ...MOCK_QUERY, scope: 'merge_requests' } });
});
- it('enables the button', () => {
- expect(findApplyButton().attributes('disabled')).toBe(undefined);
- });
- });
- });
-
- describe('ResetLinkButton', () => {
- describe('with no filter selected', () => {
- beforeEach(() => {
- createComponent({ urlQuery: {} });
+ it('shows section', () => {
+ expect(findSidebarSection().exists()).toBe(true);
});
- it('does not render', () => {
- expect(findResetLinkButton().exists()).toBe(false);
+ it('shows filters', () => {
+ expect(findFilters().exists()).toBe(true);
});
});
- describe('with filter selected', () => {
+ describe('scope=issues', () => {
beforeEach(() => {
createComponent({ urlQuery: MOCK_QUERY });
});
-
- it('does render', () => {
- expect(findResetLinkButton().exists()).toBe(true);
- });
- });
-
- describe('with filter selected and user updated query back to default', () => {
- beforeEach(() => {
- createComponent({ urlQuery: MOCK_QUERY, query: {} });
+ it('shows section', () => {
+ expect(findSidebarSection().exists()).toBe(true);
});
- it('does render', () => {
- expect(findResetLinkButton().exists()).toBe(true);
+ it('shows filters', () => {
+ expect(findFilters().exists()).toBe(true);
});
});
});
-
- describe('actions', () => {
- beforeEach(() => {
- createComponent({});
- });
-
- it('clicking ApplyButton calls applyQuery', () => {
- findSidebarForm().trigger('submit');
-
- expect(actionSpies.applyQuery).toHaveBeenCalled();
- });
-
- it('clicking ResetLinkButton calls resetQuery', () => {
- findResetLinkButton().vm.$emit('click');
-
- expect(actionSpies.resetQuery).toHaveBeenCalled();
- });
- });
});
diff --git a/spec/frontend/search/sidebar/components/filters_spec.js b/spec/frontend/search/sidebar/components/filters_spec.js
new file mode 100644
index 00000000000..4f217709297
--- /dev/null
+++ b/spec/frontend/search/sidebar/components/filters_spec.js
@@ -0,0 +1,132 @@
+import { GlButton, GlLink } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { MOCK_QUERY } from 'jest/search/mock_data';
+import ResultsFilters from '~/search/sidebar/components/results_filters.vue';
+import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
+import StatusFilter from '~/search/sidebar/components/status_filter.vue';
+
+Vue.use(Vuex);
+
+describe('GlobalSearchSidebarFilters', () => {
+ let wrapper;
+
+ const actionSpies = {
+ applyQuery: jest.fn(),
+ resetQuery: jest.fn(),
+ };
+
+ const createComponent = (initialState) => {
+ const store = new Vuex.Store({
+ state: {
+ urlQuery: MOCK_QUERY,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(ResultsFilters, {
+ store,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findSidebarForm = () => wrapper.find('form');
+ const findStatusFilter = () => wrapper.findComponent(StatusFilter);
+ const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter);
+ const findApplyButton = () => wrapper.findComponent(GlButton);
+ const findResetLinkButton = () => wrapper.findComponent(GlLink);
+
+ describe('Renders correctly', () => {
+ beforeEach(() => {
+ createComponent({ urlQuery: MOCK_QUERY });
+ });
+ it('renders StatusFilter', () => {
+ expect(findStatusFilter().exists()).toBe(true);
+ });
+
+ it('renders ConfidentialityFilter', () => {
+ expect(findConfidentialityFilter().exists()).toBe(true);
+ });
+
+ it('renders ApplyButton', () => {
+ expect(findApplyButton().exists()).toBe(true);
+ });
+ });
+
+ describe('ApplyButton', () => {
+ describe('when sidebarDirty is false', () => {
+ beforeEach(() => {
+ createComponent({ sidebarDirty: false });
+ });
+
+ it('disables the button', () => {
+ expect(findApplyButton().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('when sidebarDirty is true', () => {
+ beforeEach(() => {
+ createComponent({ sidebarDirty: true });
+ });
+
+ it('enables the button', () => {
+ expect(findApplyButton().attributes('disabled')).toBe(undefined);
+ });
+ });
+ });
+
+ describe('ResetLinkButton', () => {
+ describe('with no filter selected', () => {
+ beforeEach(() => {
+ createComponent({ urlQuery: {} });
+ });
+
+ it('does not render', () => {
+ expect(findResetLinkButton().exists()).toBe(false);
+ });
+ });
+
+ describe('with filter selected', () => {
+ beforeEach(() => {
+ createComponent({ urlQuery: MOCK_QUERY });
+ });
+
+ it('does render', () => {
+ expect(findResetLinkButton().exists()).toBe(true);
+ });
+ });
+
+ describe('with filter selected and user updated query back to default', () => {
+ beforeEach(() => {
+ createComponent({ urlQuery: MOCK_QUERY, query: {} });
+ });
+
+ it('does render', () => {
+ expect(findResetLinkButton().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('actions', () => {
+ beforeEach(() => {
+ createComponent({});
+ });
+
+ it('clicking ApplyButton calls applyQuery', () => {
+ findSidebarForm().trigger('submit');
+
+ expect(actionSpies.applyQuery).toHaveBeenCalled();
+ });
+
+ it('clicking ResetLinkButton calls resetQuery', () => {
+ findResetLinkButton().vm.$emit('click');
+
+ expect(actionSpies.resetQuery).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
new file mode 100644
index 00000000000..6262a52e01a
--- /dev/null
+++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
@@ -0,0 +1,80 @@
+import { GlNav, GlNavItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { MOCK_QUERY, MOCK_NAVIGATION } from 'jest/search/mock_data';
+import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue';
+
+Vue.use(Vuex);
+
+describe('ScopeNavigation', () => {
+ let wrapper;
+
+ const actionSpies = {
+ fetchSidebarCount: jest.fn(),
+ };
+
+ const createComponent = (initialState) => {
+ const store = new Vuex.Store({
+ state: {
+ urlQuery: MOCK_QUERY,
+ navigation: MOCK_NAVIGATION,
+ ...initialState,
+ },
+ actions: actionSpies,
+ });
+
+ wrapper = shallowMount(ScopeNavigation, {
+ store,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ 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 findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1);
+
+ describe('scope navigation', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders section', () => {
+ expect(findNavElement().exists()).toBe(true);
+ });
+
+ it('renders nav component', () => {
+ expect(findGlNav().exists()).toBe(true);
+ });
+
+ it('renders all nav item components', () => {
+ expect(findGlNavItems()).toHaveLength(9);
+ });
+
+ it('nav items have proper links', () => {
+ const linkAtPosition = 3;
+ const { link } = MOCK_NAVIGATION[Object.keys(MOCK_NAVIGATION)[linkAtPosition]];
+
+ expect(findGlNavItems().at(linkAtPosition).attributes('href')).toBe(link);
+ });
+ });
+
+ describe('scope navigation sets proper state', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets proper class to active item', () => {
+ expect(findGlNavItemActive()).toHaveLength(1);
+ });
+
+ it('active item', () => {
+ expect(findGlNavItemActiveCount().text()).toBe('2.4K');
+ });
+ });
+});
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index c442ffa521d..3d19b27ff86 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { createAlert } from '~/flash';
+import * as logger from '~/lib/logger';
import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility';
import * as actions from '~/search/store/actions';
@@ -23,6 +24,9 @@ import {
MOCK_FRESH_DATA_RES,
PRELOAD_EXPECTED_MUTATIONS,
PROMISE_ALL_EXPECTED_MUTATIONS,
+ MOCK_NAVIGATION_DATA,
+ MOCK_NAVIGATION_ACTION_MUTATION,
+ MOCK_ENDPOINT_RESPONSE,
} from '../mock_data';
jest.mock('~/flash');
@@ -31,6 +35,9 @@ jest.mock('~/lib/utils/url_utility', () => ({
joinPaths: jest.fn().mockReturnValue(''),
visitUrl: jest.fn(),
}));
+jest.mock('~/lib/logger', () => ({
+ logError: jest.fn(),
+}));
describe('Global Search Store Actions', () => {
let mock;
@@ -260,4 +267,32 @@ describe('Global Search Store Actions', () => {
);
});
});
+
+ describe.each`
+ action | axiosMock | type | scope | expectedMutations | errorLogs
+ ${actions.fetchSidebarCount} | ${{ method: 'onGet', code: 200 }} | ${'success'} | ${'issues'} | ${[MOCK_NAVIGATION_ACTION_MUTATION]} | ${0}
+ ${actions.fetchSidebarCount} | ${{ method: null, code: 0 }} | ${'success'} | ${'projects'} | ${[]} | ${0}
+ ${actions.fetchSidebarCount} | ${{ method: 'onGet', code: 500 }} | ${'error'} | ${'issues'} | ${[]} | ${1}
+ `('fetchSidebarCount', ({ action, axiosMock, type, expectedMutations, scope, errorLogs }) => {
+ describe(`on ${type}`, () => {
+ beforeEach(() => {
+ state.navigation = MOCK_NAVIGATION_DATA;
+ state.urlQuery = {
+ scope,
+ };
+
+ if (axiosMock.method) {
+ mock[axiosMock.method]().reply(axiosMock.code, MOCK_ENDPOINT_RESPONSE);
+ }
+ });
+
+ it(`should ${expectedMutations.length === 0 ? 'NOT ' : ''}dispatch ${
+ expectedMutations.length === 0 ? '' : 'the correct '
+ }mutations for ${scope}`, () => {
+ return testAction({ action, state, expectedMutations }).then(() => {
+ expect(logger.logError).toHaveBeenCalledTimes(errorLogs);
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/search/store/mutations_spec.js b/spec/frontend/search/store/mutations_spec.js
index 25f9b692955..a79ec8f70b0 100644
--- a/spec/frontend/search/store/mutations_spec.js
+++ b/spec/frontend/search/store/mutations_spec.js
@@ -1,13 +1,20 @@
import * as types from '~/search/store/mutation_types';
import mutations from '~/search/store/mutations';
import createState from '~/search/store/state';
-import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data';
+import {
+ MOCK_QUERY,
+ MOCK_GROUPS,
+ MOCK_PROJECTS,
+ MOCK_NAVIGATION_DATA,
+ MOCK_NAVIGATION_ACTION_MUTATION,
+ MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION,
+} from '../mock_data';
describe('Global Search Store Mutations', () => {
let state;
beforeEach(() => {
- state = createState({ query: MOCK_QUERY });
+ state = createState({ query: MOCK_QUERY, navigation: MOCK_NAVIGATION_DATA });
});
describe('REQUEST_GROUPS', () => {
@@ -90,4 +97,15 @@ describe('Global Search Store Mutations', () => {
expect(state.frequentItems[payload.key]).toStrictEqual(payload.data);
});
});
+
+ describe('RECEIVE_NAVIGATION_COUNT', () => {
+ it('sets frequentItems[key] to data', () => {
+ const { payload } = MOCK_NAVIGATION_ACTION_MUTATION;
+ mutations[types.RECEIVE_NAVIGATION_COUNT](state, payload);
+
+ expect(state.navigation[payload.key]).toStrictEqual(
+ MOCK_DATA_FOR_NAVIGATION_ACTION_MUTATION[payload.key],
+ );
+ });
+ });
});
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index 4348a57d055..a844d65157d 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -1023,6 +1023,7 @@ RSpec.describe SearchHelper do
describe '.search_navigation_json' do
using RSpec::Parameterized::TableSyntax
+
context 'with data' do
example_data_1 = {
projects: { label: _("Projects"), condition: true },
@@ -1041,13 +1042,13 @@ RSpec.describe SearchHelper do
}
where(:data, :matcher) do
- example_data_1 | -> { include("projects") }
- example_data_2 | -> { eq("{}") }
- example_data_3 | -> { include("projects", "blobs", "epics") }
+ example_data_1 | -> { include("projects") }
+ example_data_2 | -> { eq("{}") }
+ example_data_3 | -> { include("projects", "blobs", "epics") }
end
with_them do
- it 'converts correctly' do
+ it 'renders data correctly' do
allow(self).to receive(:search_navigation).with(no_args).and_return(data)
expect(search_navigation_json).to instance_exec(&matcher)
@@ -1056,6 +1057,23 @@ RSpec.describe SearchHelper do
end
end
+ describe '.search_navigation_json with .search_navigation' do
+ before do
+ allow(self).to receive(:current_user).and_return(build(:user))
+ allow(self).to receive(:can?).and_return(true)
+ allow(self).to receive(:project_search_tabs?).and_return(true)
+ allow(self).to receive(:feature_flag_tab_enabled?).and_return(true)
+ allow(search_service).to receive(:show_elasticsearch_tabs?).and_return(true)
+ allow(self).to receive(:feature_flag_tab_enabled?).and_return(true)
+ @show_snippets = true
+ @project = nil
+ end
+
+ it 'test search navigation item order for CE all options enabled' do
+ expect(Gitlab::Json.parse(search_navigation_json).keys).to eq(%w[projects blobs issues merge_requests wiki_blobs commits notes milestones users snippet_titles])
+ end
+ end
+
describe '.search_filter_link_json' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index 3df278a49dd..fce5a3bc7bb 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -37,18 +37,8 @@ RSpec.describe Ci::Bridge do
describe '#retryable?' do
let(:bridge) { create(:ci_bridge, :success) }
- it 'returns true' do
- expect(bridge.retryable?).to eq(true)
- end
-
- context 'without ci_recreate_downstream_pipeline ff' do
- before do
- stub_feature_flags(ci_recreate_downstream_pipeline: false)
- end
-
- it 'returns false' do
- expect(bridge.retryable?).to eq(false)
- end
+ it 'returns false' do
+ expect(bridge.retryable?).to eq(false)
end
end
diff --git a/spec/models/ci/processable_spec.rb b/spec/models/ci/processable_spec.rb
index 261eba73424..535999f211d 100644
--- a/spec/models/ci/processable_spec.rb
+++ b/spec/models/ci/processable_spec.rb
@@ -286,12 +286,6 @@ RSpec.describe Ci::Processable do
end
end
- context 'when the processable is a bridge' do
- subject(:processable) { create(:ci_bridge, pipeline: pipeline) }
-
- it_behaves_like 'retryable processable'
- end
-
context 'when the processable is a build' do
subject(:processable) { create(:ci_build, pipeline: pipeline) }
diff --git a/spec/services/ci/retry_job_service_spec.rb b/spec/services/ci/retry_job_service_spec.rb
index 5737303aaf8..540e700efa6 100644
--- a/spec/services/ci/retry_job_service_spec.rb
+++ b/spec/services/ci/retry_job_service_spec.rb
@@ -27,22 +27,6 @@ RSpec.describe Ci::RetryJobService do
project.add_reporter(reporter)
end
- shared_context 'retryable bridge' do
- let_it_be(:downstream_project) { create(:project, :repository) }
-
- let_it_be_with_refind(:job) do
- create(:ci_bridge, :success,
- pipeline: pipeline, downstream: downstream_project, description: 'a trigger job', ci_stage: stage
- )
- end
-
- let_it_be(:job_to_clone) { job }
-
- before do
- job.update!(retried: false)
- end
- end
-
shared_context 'retryable build' do
let_it_be_with_reload(:job) do
create(:ci_build, :success, pipeline: pipeline, ci_stage: stage)
@@ -211,20 +195,6 @@ RSpec.describe Ci::RetryJobService do
expect { service.clone!(create(:ci_build).present) }.to raise_error(TypeError)
end
- context 'when the job to be cloned is a bridge' do
- include_context 'retryable bridge'
-
- it_behaves_like 'clones the job'
-
- context 'when given variables' do
- let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
-
- it 'does not give variables to the new bridge' do
- expect { new_job }.not_to raise_error
- end
- end
- end
-
context 'when the job to be cloned is a build' do
include_context 'retryable build'
@@ -331,20 +301,6 @@ RSpec.describe Ci::RetryJobService do
subject { service.execute(job) }
- context 'when the job to be retried is a bridge' do
- include_context 'retryable bridge'
-
- it_behaves_like 'retries the job'
-
- context 'when given variables' do
- let(:new_job) { service.clone!(job, variables: job_variables_attributes) }
-
- it 'does not give variables to the new bridge' do
- expect { new_job }.not_to raise_error
- end
- end
- end
-
context 'when the job to be retried is a build' do
include_context 'retryable build'
diff --git a/spec/views/shared/deploy_tokens/_form.html.haml_spec.rb b/spec/views/shared/deploy_tokens/_form.html.haml_spec.rb
deleted file mode 100644
index 74ad0ccb77a..00000000000
--- a/spec/views/shared/deploy_tokens/_form.html.haml_spec.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe 'shared/deploy_tokens/_form.html.haml' do
- using RSpec::Parameterized::TableSyntax
-
- let_it_be(:user) { create(:user) }
- let_it_be(:token) { build(:deploy_token) }
-
- RSpec.shared_examples "display deploy token settings" do |role, shows_package_registry_permissions|
- before do
- subject.add_member(user, role)
- allow(view).to receive(:current_user).and_return(user)
- stub_config(packages: { enabled: packages_enabled })
- end
-
- it "correctly renders the form" do
- render 'shared/deploy_tokens/form', token: token, group_or_project: subject
-
- if shows_package_registry_permissions
- expect(rendered).to have_content('Allows read-only access to the package registry.')
- else
- expect(rendered).not_to have_content('Allows read-only access to the package registry.')
- end
- end
- end
-
- context "when the subject is a project" do
- let_it_be(:subject, refind: true) { create(:project, :private) }
-
- where(:packages_enabled, :feature_enabled, :role, :shows_package_registry_permissions) do
- true | true | :maintainer | true
- false | true | :maintainer | false
- true | false | :maintainer | false
- false | false | :maintainer | false
- end
-
- with_them do
- before do
- subject.update!(packages_enabled: feature_enabled)
- end
-
- it_behaves_like 'display deploy token settings', params[:role], params[:shows_package_registry_permissions]
- end
- end
-
- context "when the subject is a group" do
- let_it_be(:subject, refind: true) { create(:group, :private) }
-
- where(:packages_enabled, :role, :shows_package_registry_permissions) do
- true | :owner | true
- false | :owner | false
- true | :maintainer | true
- false | :maintainer | false
- end
-
- with_them do
- it_behaves_like 'display deploy token settings', params[:role], params[:shows_package_registry_permissions]
- end
- end
-end