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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml5
-rw-r--r--.rubocop_todo.yml37
-rw-r--r--.rubocop_todo/performance/collection_literal_in_loop.yml38
-rw-r--r--.rubocop_todo/rails/helper_instance_variable.yml85
-rw-r--r--.rubocop_todo/rails/rake_environment.yml25
-rw-r--r--.rubocop_todo/rspec/return_from_stub.yml319
-rw-r--r--.rubocop_todo/style/format_string.yml360
-rw-r--r--.rubocop_todo/style/single_argument_dig.yml64
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock7
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue5
-rw-r--r--app/assets/javascripts/mr_notes/index.js8
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue113
-rw-r--r--app/assets/javascripts/notes/stores/actions.js17
-rw-r--r--app/assets/javascripts/pipelines/pipeline_tabs.js13
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue11
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js22
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js35
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue15
-rw-r--r--app/assets/javascripts/vue_shared/components/deployment_instance.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss3
-rw-r--r--app/assets/stylesheets/pages/notes.scss29
-rw-r--r--app/controllers/admin/batched_jobs_controller.rb28
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb3
-rw-r--r--app/controllers/projects/logs_controller.rb2
-rw-r--r--app/controllers/projects/metrics_dashboard_controller.rb1
-rw-r--r--app/finders/user_recent_events_finder.rb7
-rw-r--r--app/helpers/projects/pipeline_helper.rb1
-rw-r--r--app/helpers/sorting_helper.rb45
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/ci/pipeline.rb8
-rw-r--r--app/models/event_collection.rb47
-rw-r--r--app/serializers/cluster_entity.rb10
-rw-r--r--app/serializers/environment_entity.rb3
-rw-r--r--app/services/ci/pipeline_creation/start_pipeline_service.rb4
-rw-r--r--app/services/merge_requests/base_service.rb12
-rw-r--r--app/services/notes/create_service.rb7
-rw-r--r--app/services/quick_actions/interpret_service.rb11
-rw-r--r--app/views/admin/background_migrations/_job.html.haml3
-rw-r--r--app/views/admin/background_migrations/_migration.html.haml2
-rw-r--r--app/views/admin/background_migrations/index.html.haml1
-rw-r--r--app/views/admin/background_migrations/show.html.haml7
-rw-r--r--app/views/admin/batched_jobs/_job.html.haml17
-rw-r--r--app/views/admin/batched_jobs/_transition_log.html.haml13
-rw-r--r--app/views/admin/batched_jobs/show.html.haml36
-rw-r--r--app/views/clusters/clusters/_integrations.html.haml21
-rw-r--r--app/views/notify/merge_request_unmergeable_email.html.haml8
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/services/_form.html.haml8
-rw-r--r--app/views/shared/_integration_settings.html.haml4
-rw-r--r--app/views/shared/integrations/mattermost_slash_commands/_detailed_help.html.haml (renamed from app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml)0
-rw-r--r--app/views/shared/integrations/mattermost_slash_commands/_help.html.haml (renamed from app/views/projects/services/mattermost_slash_commands/_help.html.haml)4
-rw-r--r--app/views/shared/integrations/mattermost_slash_commands/_installation_info.html.haml (renamed from app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml)0
-rw-r--r--app/views/shared/integrations/prometheus/_custom_metrics.html.haml (renamed from app/views/projects/services/prometheus/_custom_metrics.html.haml)0
-rw-r--r--app/views/shared/integrations/prometheus/_help.html.haml (renamed from app/views/projects/services/prometheus/_help.html.haml)0
-rw-r--r--app/views/shared/integrations/prometheus/_metrics.html.haml (renamed from app/views/projects/services/prometheus/_metrics.html.haml)2
-rw-r--r--app/views/shared/integrations/prometheus/_show.html.haml (renamed from app/views/projects/services/prometheus/_show.html.haml)2
-rw-r--r--app/views/shared/integrations/slack/_help.haml (renamed from app/views/projects/services/slack/_help.haml)0
-rw-r--r--app/views/shared/integrations/slack_slash_commands/_help.html.haml (renamed from app/views/projects/services/slack_slash_commands/_help.html.haml)0
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml27
-rw-r--r--config/feature_flags/development/monitor_logging.yml8
-rw-r--r--config/feature_flags/development/optimized_project_and_group_activity_queries.yml (renamed from config/feature_flags/development/ci_reduce_persistent_ref_writes.yml)10
-rw-r--r--config/routes/admin.rb2
-rw-r--r--data/removals/15_0/15-0-dependency-scanning-python-image.yml11
-rw-r--r--db/post_migrate/20220422121443_add_async_index_for_group_activity_events.rb13
-rw-r--r--db/post_migrate/20220425111114_add_async_index_for_project_activity_events.rb13
-rw-r--r--db/post_migrate/20220425111453_add_async_index_to_events_on_group_id_and_id.rb13
-rw-r--r--db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb18
-rw-r--r--db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb31
-rw-r--r--db/schema_migrations/202204221214431
-rw-r--r--db/schema_migrations/202204251111141
-rw-r--r--db/schema_migrations/202204251114531
-rw-r--r--db/schema_migrations/202205130433441
-rw-r--r--doc/administration/instance_limits.md8
-rw-r--r--doc/api/personal_access_tokens.md27
-rw-r--r--doc/development/integrations/index.md2
-rw-r--r--doc/development/testing_guide/frontend_testing.md5
-rw-r--r--doc/integration/kerberos.md8
-rw-r--r--doc/operations/index.md13
-rw-r--r--doc/update/removals.md10
-rw-r--r--doc/user/application_security/container_scanning/index.md56
-rw-r--r--doc/user/application_security/dependency_scanning/index.md12
-rw-r--r--doc/user/application_security/index.md2
-rw-r--r--doc/user/clusters/integrations.md16
-rw-r--r--doc/user/clusters/management_project_template.md1
-rw-r--r--doc/user/discussions/img/add_internal_note_v15_0.pngbin0 -> 66042 bytes
-rw-r--r--doc/user/discussions/img/confidential_comments_v15_0.pngbin12775 -> 0 bytes
-rw-r--r--doc/user/discussions/index.md29
-rw-r--r--doc/user/group/iterations/index.md12
-rw-r--r--doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md27
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md10
-rw-r--r--doc/user/project/issues/confidential_issues.md2
-rw-r--r--doc/user/project/merge_requests/confidential.md2
-rw-r--r--lib/api/notes.rb2
-rw-r--r--lib/api/personal_access_tokens.rb17
-rw-r--r--lib/event_filter.rb58
-rw-r--r--lib/gitlab/application_rate_limiter.rb2
-rw-r--r--lib/gitlab/background_migration/expire_o_auth_tokens.rb6
-rw-r--r--lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml79
-rw-r--r--lib/gitlab/instrumentation/rate_limiting_gates.rb33
-rw-r--r--lib/gitlab/instrumentation_helper.rb5
-rw-r--r--lib/gitlab/omniauth_initializer.rb2
-rw-r--r--lib/sidebars/projects/menus/monitor_menu.rb3
-rw-r--r--locale/gitlab.pot105
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/project/settings/services/prometheus.rb2
-rw-r--r--spec/controllers/projects/logs_controller_spec.rb14
-rw-r--r--spec/features/admin/admin_sees_background_migrations_spec.rb13
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb4
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb11
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/batch_comments_spec.rb12
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb86
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb14
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb6
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb29
-rw-r--r--spec/features/projects/pipelines/legacy_pipeline_spec.rb1073
-rw-r--r--spec/features/projects/pipelines/legacy_pipelines_spec.rb847
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb72
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb1
-rw-r--r--spec/features/user_sorts_things_spec.rb12
-rw-r--r--spec/frontend/.eslintrc.yml3
-rw-r--r--spec/frontend/__helpers__/fixtures.js15
-rw-r--r--spec/frontend/__helpers__/shared_test_setup.js7
-rw-r--r--spec/frontend/activities_spec.js7
-rw-r--r--spec/frontend/admin/applications/components/delete_application_spec.js4
-rw-r--r--spec/frontend/admin/users/new_spec.js7
-rw-r--r--spec/frontend/alert_handler_spec.js18
-rw-r--r--spec/frontend/attention_requests/components/navigation_popover_spec.js4
-rw-r--r--spec/frontend/authentication/u2f/authenticate_spec.js7
-rw-r--r--spec/frontend/authentication/u2f/register_spec.js7
-rw-r--r--spec/frontend/authentication/webauthn/authenticate_spec.js7
-rw-r--r--spec/frontend/authentication/webauthn/register_spec.js7
-rw-r--r--spec/frontend/awards_handler_spec.js5
-rw-r--r--spec/frontend/badges/components/badge_form_spec.js4
-rw-r--r--spec/frontend/badges/components/badge_list_row_spec.js4
-rw-r--r--spec/frontend/badges/components/badge_list_spec.js4
-rw-r--r--spec/frontend/badges/components/badge_spec.js7
-rw-r--r--spec/frontend/behaviors/autosize_spec.js20
-rw-r--r--spec/frontend/behaviors/date_picker_spec.js7
-rw-r--r--spec/frontend/behaviors/load_startup_css_spec.js6
-rw-r--r--spec/frontend/behaviors/markdown/highlight_current_user_spec.js7
-rw-r--r--spec/frontend/behaviors/markdown/render_sandboxed_mermaid_spec.js5
-rw-r--r--spec/frontend/behaviors/quick_submit_spec.js7
-rw-r--r--spec/frontend/behaviors/requires_input_spec.js7
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js7
-rw-r--r--spec/frontend/blob/blob_file_dropzone_spec.js7
-rw-r--r--spec/frontend/blob/components/table_contents_spec.js4
-rw-r--r--spec/frontend/blob/file_template_mediator_spec.js7
-rw-r--r--spec/frontend/blob/line_highlighter_spec.js8
-rw-r--r--spec/frontend/blob/openapi/index_spec.js7
-rw-r--r--spec/frontend/blob/sketch/index_spec.js7
-rw-r--r--spec/frontend/blob/viewer/index_spec.js5
-rw-r--r--spec/frontend/blob_edit/blob_bundle_spec.js14
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js4
-rw-r--r--spec/frontend/bootstrap_jquery_spec.js13
-rw-r--r--spec/frontend/bootstrap_linked_tabs_spec.js7
-rw-r--r--spec/frontend/broadcast_notification_spec.js7
-rw-r--r--spec/frontend/ci_variable_list/ci_variable_list/ci_variable_list_spec.js19
-rw-r--r--spec/frontend/ci_variable_list/ci_variable_list/native_form_variable_list_spec.js7
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js8
-rw-r--r--spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js7
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js6
-rw-r--r--spec/frontend/code_navigation/store/actions_spec.js7
-rw-r--r--spec/frontend/code_navigation/utils/index_spec.js9
-rw-r--r--spec/frontend/commits_spec.js7
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js7
-rw-r--r--spec/frontend/create_item_dropdown_spec.js4
-rw-r--r--spec/frontend/deprecated_jquery_dropdown_spec.js5
-rw-r--r--spec/frontend/dropzone_input_spec.js5
-rw-r--r--spec/frontend/editor/source_editor_ci_schema_ext_spec.js5
-rw-r--r--spec/frontend/editor/source_editor_extension_base_spec.js6
-rw-r--r--spec/frontend/editor/source_editor_markdown_ext_spec.js5
-rw-r--r--spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js4
-rw-r--r--spec/frontend/editor/source_editor_spec.js8
-rw-r--r--spec/frontend/editor/source_editor_yaml_ext_spec.js9
-rw-r--r--spec/frontend/editor/utils_spec.js13
-rw-r--r--spec/frontend/filterable_list_spec.js6
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js7
-rw-r--r--spec/frontend/filtered_search/dropdown_utils_spec.js11
-rw-r--r--spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js7
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js8
-rw-r--r--spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js9
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js7
-rw-r--r--spec/frontend/flash_spec.js6
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js4
-rw-r--r--spec/frontend/gl_field_errors_spec.js7
-rw-r--r--spec/frontend/google_tag_manager/index_spec.js10
-rw-r--r--spec/frontend/gpg_badges_spec.js10
-rw-r--r--spec/frontend/header_spec.js10
-rw-r--r--spec/frontend/helpers/startup_css_helper_spec.js9
-rw-r--r--spec/frontend/ide/components/commit_sidebar/message_field_spec.js5
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js7
-rw-r--r--spec/frontend/image_diff/init_discussion_tab_spec.js7
-rw-r--r--spec/frontend/image_diff/replaced_image_diff_spec.js7
-rw-r--r--spec/frontend/issuable/issuable_form_spec.js10
-rw-r--r--spec/frontend/issues/issue_spec.js9
-rw-r--r--spec/frontend/issues/show/components/app_spec.js4
-rw-r--r--spec/frontend/issues/show/components/title_spec.js7
-rw-r--r--spec/frontend/lib/utils/dom_utils_spec.js13
-rw-r--r--spec/frontend/lib/utils/file_upload_spec.js7
-rw-r--r--spec/frontend/lib/utils/navigation_utility_spec.js5
-rw-r--r--spec/frontend/lib/utils/resize_observer_spec.js4
-rw-r--r--spec/frontend/listbox/index_spec.js6
-rw-r--r--spec/frontend/merge_request_spec.js6
-rw-r--r--spec/frontend/merge_request_tabs_spec.js7
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js26
-rw-r--r--spec/frontend/new_branch_spec.js7
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js41
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js32
-rw-r--r--spec/frontend/notes/deprecated_notes_spec.js11
-rw-r--r--spec/frontend/notes/stores/actions_spec.js4
-rw-r--r--spec/frontend/oauth_remember_me_spec.js7
-rw-r--r--spec/frontend/pager_spec.js31
-rw-r--r--spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js8
-rw-r--r--spec/frontend/pages/admin/application_settings/account_and_limits_spec.js7
-rw-r--r--spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js7
-rw-r--r--spec/frontend/pages/admin/projects/components/namespace_select_spec.js7
-rw-r--r--spec/frontend/pages/dashboard/todos/index/todos_spec.js7
-rw-r--r--spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js5
-rw-r--r--spec/frontend/pages/projects/pages_domains/form_spec.js7
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js9
-rw-r--r--spec/frontend/pages/search/show/refresh_counts_spec.js7
-rw-r--r--spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js7
-rw-r--r--spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js7
-rw-r--r--spec/frontend/performance_bar/index_spec.js4
-rw-r--r--spec/frontend/pipelines/components/pipeline_tabs_spec.js2
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js15
-rw-r--r--spec/frontend/project_select_combo_button_spec.js7
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js4
-rw-r--r--spec/frontend/projects/new/components/deployment_target_select_spec.js4
-rw-r--r--spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js4
-rw-r--r--spec/frontend/projects/project_import_gitlab_project_spec.js4
-rw-r--r--spec/frontend/projects/project_new_spec.js7
-rw-r--r--spec/frontend/projects/projects_filterable_list_spec.js6
-rw-r--r--spec/frontend/projects/settings/access_dropdown_spec.js7
-rw-r--r--spec/frontend/prometheus_metrics/custom_metrics_spec.js4
-rw-r--r--spec/frontend/prometheus_metrics/prometheus_metrics_spec.js7
-rw-r--r--spec/frontend/protected_branches/protected_branch_create_spec.js7
-rw-r--r--spec/frontend/protected_branches/protected_branch_edit_spec.js10
-rw-r--r--spec/frontend/read_more_spec.js7
-rw-r--r--spec/frontend/right_sidebar_spec.js6
-rw-r--r--spec/frontend/search/highlight_blob_search_result_spec.js7
-rw-r--r--spec/frontend/search_autocomplete_spec.js5
-rw-r--r--spec/frontend/settings_panels_spec.js7
-rw-r--r--spec/frontend/shortcuts_spec.js7
-rw-r--r--spec/frontend/single_file_diff_spec.js3
-rw-r--r--spec/frontend/smart_interval_spec.js7
-rw-r--r--spec/frontend/snippet/collapsible_input_spec.js6
-rw-r--r--spec/frontend/syntax_highlight_spec.js16
-rw-r--r--spec/frontend/tabs/index_spec.js8
-rw-r--r--spec/frontend/task_list_spec.js7
-rw-r--r--spec/frontend/user_popovers_spec.js8
-rw-r--r--spec/frontend/vue_alerts_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js4
-rw-r--r--spec/frontend/vue_shared/directives/autofocusonshow_spec.js7
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js4
-rw-r--r--spec/frontend/whats_new/utils/notification_spec.js4
-rw-r--r--spec/frontend/wikis_spec.js6
-rw-r--r--spec/frontend/zen_mode_spec.js7
-rw-r--r--spec/frontend_integration/ide/ide_integration_spec.js4
-rw-r--r--spec/frontend_integration/ide/user_opens_file_spec.js4
-rw-r--r--spec/frontend_integration/ide/user_opens_ide_spec.js4
-rw-r--r--spec/frontend_integration/ide/user_opens_mr_spec.js4
-rw-r--r--spec/frontend_integration/lib/utils/browser_spec.js9
-rw-r--r--spec/helpers/projects/pipeline_helper_spec.rb1
-rw-r--r--spec/lib/gitlab/application_rate_limiter_spec.rb14
-rw-r--r--spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb2
-rw-r--r--spec/lib/gitlab/instrumentation/rate_limiting_gates_spec.rb39
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb21
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb3
-rw-r--r--spec/lib/sidebars/projects/menus/monitor_menu_spec.rb8
-rw-r--r--spec/mailers/emails/merge_requests_spec.rb2
-rw-r--r--spec/migrations/20220513043344_reschedule_expire_o_auth_tokens_spec.rb (renamed from spec/migrations/20220428133724_schedule_expire_o_auth_tokens_spec.rb)2
-rw-r--r--spec/models/ci/build_spec.rb13
-rw-r--r--spec/models/ci/pipeline_spec.rb26
-rw-r--r--spec/models/event_collection_spec.rb248
-rw-r--r--spec/requests/admin/batched_jobs_controller_spec.rb74
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb2
-rw-r--r--spec/requests/api/personal_access_tokens_spec.rb48
-rw-r--r--spec/serializers/cluster_entity_spec.rb16
-rw-r--r--spec/serializers/environment_entity_spec.rb12
-rw-r--r--spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb12
-rw-r--r--spec/services/merge_requests/update_service_spec.rb2
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb8
-rw-r--r--spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb3
-rw-r--r--yarn.lock12
297 files changed, 5216 insertions, 977 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index aefb24e0b5d..c99d3d0b6ab 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -266,6 +266,11 @@ Rails/IndexBy:
Rails/UniqueValidationWithoutIndex:
Enabled: false
+Rails/HelperInstanceVariable:
+ Include:
+ - app/helpers/**/*.rb
+ - ee/app/helpers/**/*.rb
+
# GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index e4db93d1602..4d2477c199d 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -11,11 +11,6 @@ Gitlab/PolicyRuleBoolean:
Exclude:
- 'ee/app/policies/ee/identity_provider_policy.rb'
-# Offense count: 29
-# Configuration parameters: MinSize.
-Performance/CollectionLiteralInLoop:
- Enabled: false
-
# Offense count: 41
# Cop supports --auto-correct.
Performance/ConstantRegexp:
@@ -39,13 +34,6 @@ RSpec/PredicateMatcher:
RSpec/RepeatedExampleGroupBody:
Enabled: false
-# Offense count: 667
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle.
-# SupportedStyles: and_return, block
-RSpec/ReturnFromStub:
- Enabled: false
-
# Offense count: 610
# Cop supports --auto-correct.
RSpec/ScatteredLet:
@@ -90,12 +78,6 @@ Rails/CreateTableWithTimestamps:
Rails/HasManyOrHasOneDependent:
Enabled: false
-# Offense count: 537
-# Configuration parameters: Include.
-# Include: app/helpers/**/*.rb
-Rails/HelperInstanceVariable:
- Enabled: false
-
# Offense count: 47
# Cop supports --auto-correct.
Rails/IndexWith:
@@ -120,13 +102,6 @@ Rails/MailerName:
Rails/NegateInclude:
Enabled: false
-# Offense count: 39
-# Cop supports --auto-correct.
-# Configuration parameters: Include.
-# Include: **/Rakefile, **/*.rake
-Rails/RakeEnvironment:
- Enabled: false
-
# Offense count: 44
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -160,13 +135,6 @@ Style/CaseLikeIf:
Style/EmptyMethod:
Enabled: false
-# Offense count: 581
-# Cop supports --auto-correct.
-# Configuration parameters: EnforcedStyle.
-# SupportedStyles: format, sprintf, percent
-Style/FormatString:
- Enabled: false
-
# Offense count: 59
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
@@ -195,8 +163,3 @@ Style/RedundantRegexpEscape:
# Cop supports --auto-correct.
Style/RescueModifier:
Enabled: false
-
-# Offense count: 123
-# Cop supports --auto-correct.
-Style/SingleArgumentDig:
- Enabled: false
diff --git a/.rubocop_todo/performance/collection_literal_in_loop.yml b/.rubocop_todo/performance/collection_literal_in_loop.yml
new file mode 100644
index 00000000000..4b012bf6645
--- /dev/null
+++ b/.rubocop_todo/performance/collection_literal_in_loop.yml
@@ -0,0 +1,38 @@
+---
+Performance/CollectionLiteralInLoop:
+ # Offense count: 45
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'config/application.rb'
+ - 'config/initializers/1_settings.rb'
+ - 'ee/app/models/ee/merge_request.rb'
+ - 'ee/spec/features/admin/admin_settings_spec.rb'
+ - 'ee/spec/support/shared_examples/features/protected_branches_access_control_shared_examples.rb'
+ - 'ee/spec/workers/app_sec/dast/profile_schedule_worker_spec.rb'
+ - 'ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb'
+ - 'lib/gitlab/fake_application_settings.rb'
+ - 'lib/gitlab/otp_key_rotator.rb'
+ - 'lib/gitlab/reference_extractor.rb'
+ - 'lib/gitlab/sidekiq_middleware/server_metrics.rb'
+ - 'lib/gitlab/tracking/incident_management.rb'
+ - 'lib/quality/seeders/issues.rb'
+ - 'lib/tasks/gitlab/seed/group_seed.rake'
+ - 'spec/bin/sidekiq_cluster_spec.rb'
+ - 'spec/controllers/groups_controller_spec.rb'
+ - 'spec/lib/banzai/reference_parser/base_parser_spec.rb'
+ - 'spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb'
+ - 'spec/lib/gitlab/file_detector_spec.rb'
+ - 'spec/lib/gitlab/search/abuse_detection_spec.rb'
+ - 'spec/lib/gitlab/utils/markdown_spec.rb'
+ - 'spec/models/analytics/cycle_analytics/aggregation_spec.rb'
+ - 'spec/models/ci/build_spec.rb'
+ - 'spec/models/ci/pipeline_spec.rb'
+ - 'spec/models/namespace_statistics_spec.rb'
+ - 'spec/presenters/ci/build_runner_presenter_spec.rb'
+ - 'spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb'
+ - 'spec/presenters/packages/nuget/service_index_presenter_spec.rb'
+ - 'spec/requests/api/ci/pipelines_spec.rb'
+ - 'spec/requests/api/integrations_spec.rb'
+ - 'spec/requests/api/project_container_repositories_spec.rb'
+ - 'spec/support/shared_examples/models/email_format_shared_examples.rb'
diff --git a/.rubocop_todo/rails/helper_instance_variable.yml b/.rubocop_todo/rails/helper_instance_variable.yml
new file mode 100644
index 00000000000..8a1a6093c3d
--- /dev/null
+++ b/.rubocop_todo/rails/helper_instance_variable.yml
@@ -0,0 +1,85 @@
+---
+Rails/HelperInstanceVariable:
+ # Offense count: 671
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/helpers/admin/user_actions_helper.rb'
+ - 'app/helpers/application_helper.rb'
+ - 'app/helpers/application_settings_helper.rb'
+ - 'app/helpers/award_emoji_helper.rb'
+ - 'app/helpers/blob_helper.rb'
+ - 'app/helpers/boards_helper.rb'
+ - 'app/helpers/branches_helper.rb'
+ - 'app/helpers/breadcrumbs_helper.rb'
+ - 'app/helpers/broadcast_messages_helper.rb'
+ - 'app/helpers/ci/builds_helper.rb'
+ - 'app/helpers/ci/jobs_helper.rb'
+ - 'app/helpers/commits_helper.rb'
+ - 'app/helpers/compare_helper.rb'
+ - 'app/helpers/diff_helper.rb'
+ - 'app/helpers/emails_helper.rb'
+ - 'app/helpers/environments_helper.rb'
+ - 'app/helpers/events_helper.rb'
+ - 'app/helpers/explore_helper.rb'
+ - 'app/helpers/feature_flags_helper.rb'
+ - 'app/helpers/form_helper.rb'
+ - 'app/helpers/graph_helper.rb'
+ - 'app/helpers/groups_helper.rb'
+ - 'app/helpers/icons_helper.rb'
+ - 'app/helpers/ide_helper.rb'
+ - 'app/helpers/import_helper.rb'
+ - 'app/helpers/integrations_helper.rb'
+ - 'app/helpers/issuables_description_templates_helper.rb'
+ - 'app/helpers/issuables_helper.rb'
+ - 'app/helpers/issues_helper.rb'
+ - 'app/helpers/labels_helper.rb'
+ - 'app/helpers/markup_helper.rb'
+ - 'app/helpers/merge_requests_helper.rb'
+ - 'app/helpers/mirror_helper.rb'
+ - 'app/helpers/nav_helper.rb'
+ - 'app/helpers/notes_helper.rb'
+ - 'app/helpers/operations_helper.rb'
+ - 'app/helpers/page_layout_helper.rb'
+ - 'app/helpers/projects_helper.rb'
+ - 'app/helpers/releases_helper.rb'
+ - 'app/helpers/routing/projects_helper.rb'
+ - 'app/helpers/routing/pseudonymization_helper.rb'
+ - 'app/helpers/routing/snippets_helper.rb'
+ - 'app/helpers/search_helper.rb'
+ - 'app/helpers/selects_helper.rb'
+ - 'app/helpers/startupjs_helper.rb'
+ - 'app/helpers/submodule_helper.rb'
+ - 'app/helpers/tab_helper.rb'
+ - 'app/helpers/tags_helper.rb'
+ - 'app/helpers/timeboxes_helper.rb'
+ - 'app/helpers/tree_helper.rb'
+ - 'app/helpers/users_helper.rb'
+ - 'app/helpers/web_ide_button_helper.rb'
+ - 'app/helpers/webpack_helper.rb'
+ - 'app/helpers/wiki_helper.rb'
+ - 'ee/app/helpers/ee/application_helper.rb'
+ - 'ee/app/helpers/ee/boards_helper.rb'
+ - 'ee/app/helpers/ee/feature_flags_helper.rb'
+ - 'ee/app/helpers/ee/form_helper.rb'
+ - 'ee/app/helpers/ee/graph_helper.rb'
+ - 'ee/app/helpers/ee/groups/group_members_helper.rb'
+ - 'ee/app/helpers/ee/groups_helper.rb'
+ - 'ee/app/helpers/ee/integrations_helper.rb'
+ - 'ee/app/helpers/ee/kerberos_spnego_helper.rb'
+ - 'ee/app/helpers/ee/labels_helper.rb'
+ - 'ee/app/helpers/ee/lock_helper.rb'
+ - 'ee/app/helpers/ee/merge_requests_helper.rb'
+ - 'ee/app/helpers/ee/mirror_helper.rb'
+ - 'ee/app/helpers/ee/namespace_storage_limit_alert_helper.rb'
+ - 'ee/app/helpers/ee/notes_helper.rb'
+ - 'ee/app/helpers/ee/operations_helper.rb'
+ - 'ee/app/helpers/ee/projects/security/configuration_helper.rb'
+ - 'ee/app/helpers/ee/projects_helper.rb'
+ - 'ee/app/helpers/ee/search_helper.rb'
+ - 'ee/app/helpers/ee/selects_helper.rb'
+ - 'ee/app/helpers/ee/subscribable_banner_helper.rb'
+ - 'ee/app/helpers/ee/wiki_helper.rb'
+ - 'ee/app/helpers/path_locks_helper.rb'
+ - 'ee/app/helpers/projects/security/discover_helper.rb'
+ - 'ee/app/helpers/seats_count_alert_helper.rb'
diff --git a/.rubocop_todo/rails/rake_environment.yml b/.rubocop_todo/rails/rake_environment.yml
new file mode 100644
index 00000000000..d248db022ce
--- /dev/null
+++ b/.rubocop_todo/rails/rake_environment.yml
@@ -0,0 +1,25 @@
+---
+# Cop supports --auto-correct.
+Rails/RakeEnvironment:
+ # Offense count: 31
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'ee/lib/tasks/gitlab/elastic/test.rake'
+ - 'lib/tasks/config_lint.rake'
+ - 'lib/tasks/dev.rake'
+ - 'lib/tasks/gettext.rake'
+ - 'lib/tasks/gitlab/assets.rake'
+ - 'lib/tasks/gitlab/db.rake'
+ - 'lib/tasks/gitlab/docs/compile_deprecations.rake'
+ - 'lib/tasks/gitlab/docs/redirect.rake'
+ - 'lib/tasks/gitlab/helpers.rake'
+ - 'lib/tasks/gitlab/sidekiq.rake'
+ - 'lib/tasks/gitlab/tw/codeowners.rake'
+ - 'lib/tasks/gitlab/update_templates.rake'
+ - 'lib/tasks/lint.rake'
+ - 'lib/tasks/migrate/setup_postgresql.rake'
+ - 'lib/tasks/setup.rake'
+ - 'lib/tasks/test.rake'
+ - 'lib/tasks/yarn.rake'
+ - 'qa/qa/fixtures/auto_devops_rack/Rakefile'
diff --git a/.rubocop_todo/rspec/return_from_stub.yml b/.rubocop_todo/rspec/return_from_stub.yml
new file mode 100644
index 00000000000..da5ed01cafa
--- /dev/null
+++ b/.rubocop_todo/rspec/return_from_stub.yml
@@ -0,0 +1,319 @@
+---
+# Cop supports --auto-correct.
+RSpec/ReturnFromStub:
+ # Offense count: 703
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'ee/spec/controllers/admin/geo/nodes_controller_spec.rb'
+ - 'ee/spec/controllers/groups/billings_controller_spec.rb'
+ - 'ee/spec/controllers/groups/group_members_controller_spec.rb'
+ - 'ee/spec/controllers/profiles/billings_controller_spec.rb'
+ - 'ee/spec/controllers/projects/branches_controller_spec.rb'
+ - 'ee/spec/features/account_recovery_regular_check_spec.rb'
+ - 'ee/spec/features/admin/groups/admin_changes_plan_spec.rb'
+ - 'ee/spec/features/burndown_charts_spec.rb'
+ - 'ee/spec/features/groups/group_settings_spec.rb'
+ - 'ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb'
+ - 'ee/spec/features/projects/integrations/user_activates_jira_spec.rb'
+ - 'ee/spec/features/projects/milestones/milestone_spec.rb'
+ - 'ee/spec/features/projects/new_project_spec.rb'
+ - 'ee/spec/features/projects/pipelines/pipeline_spec.rb'
+ - 'ee/spec/features/projects/settings/ee/service_desk_setting_spec.rb'
+ - 'ee/spec/features/promotion_spec.rb'
+ - 'ee/spec/features/refactor_blob_viewer_disabled/projects/path_locks_spec.rb'
+ - 'ee/spec/features/trials/select_namespace_spec.rb'
+ - 'ee/spec/graphql/mutations/projects/set_locked_spec.rb'
+ - 'ee/spec/helpers/application_helper_spec.rb'
+ - 'ee/spec/helpers/ee/auth_helper_spec.rb'
+ - 'ee/spec/helpers/ee/ci/pipelines_helper_spec.rb'
+ - 'ee/spec/helpers/ee/groups_helper_spec.rb'
+ - 'ee/spec/helpers/ee/issues_helper_spec.rb'
+ - 'ee/spec/helpers/ee/lock_helper_spec.rb'
+ - 'ee/spec/helpers/ee/operations_helper_spec.rb'
+ - 'ee/spec/helpers/ee/security_orchestration_helper_spec.rb'
+ - 'ee/spec/helpers/nav/new_dropdown_helper_spec.rb'
+ - 'ee/spec/helpers/nav/top_nav_helper_spec.rb'
+ - 'ee/spec/helpers/preferences_helper_spec.rb'
+ - 'ee/spec/lib/banzai/filter/cross_project_issuable_information_filter_spec.rb'
+ - 'ee/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb'
+ - 'ee/spec/lib/ee/feature_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/checks/push_rules/branch_check_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/database_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/minutes/build_consumption_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/minutes/cost_factor_spec.rb'
+ - 'ee/spec/lib/gitlab/code_owners_spec.rb'
+ - 'ee/spec/lib/gitlab/geo/health_check_spec.rb'
+ - 'ee/spec/lib/gitlab/geo/logger_spec.rb'
+ - 'ee/spec/lib/gitlab/geo/replication/base_transfer_spec.rb'
+ - 'ee/spec/lib/gitlab/geo/replication/file_transfer_spec.rb'
+ - 'ee/spec/lib/gitlab/geo_spec.rb'
+ - 'ee/spec/lib/gitlab/git_access_spec.rb'
+ - 'ee/spec/lib/gitlab/git_access_wiki_spec.rb'
+ - 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb'
+ - 'ee/spec/lib/system_check/geo/authorized_keys_check_spec.rb'
+ - 'ee/spec/lib/system_check/geo/http_connection_check_spec.rb'
+ - 'ee/spec/models/ci/build_spec.rb'
+ - 'ee/spec/models/concerns/ee/project_security_scanners_information_spec.rb'
+ - 'ee/spec/models/ee/namespace_spec.rb'
+ - 'ee/spec/models/ee/user_spec.rb'
+ - 'ee/spec/models/license_spec.rb'
+ - 'ee/spec/models/merge_request/blocking_spec.rb'
+ - 'ee/spec/models/project_spec.rb'
+ - 'ee/spec/models/vulnerabilities/finding_spec.rb'
+ - 'ee/spec/policies/project_policy_spec.rb'
+ - 'ee/spec/presenters/ci/build_presenter_spec.rb'
+ - 'ee/spec/presenters/merge_request_presenter_spec.rb'
+ - 'ee/spec/requests/admin/credentials_controller_spec.rb'
+ - 'ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb'
+ - 'ee/spec/services/auto_merge/merge_train_service_spec.rb'
+ - 'ee/spec/services/deployments/auto_rollback_service_spec.rb'
+ - 'ee/spec/services/geo/design_repository_sync_service_spec.rb'
+ - 'ee/spec/services/geo/files_expire_service_spec.rb'
+ - 'ee/spec/services/geo/framework_repository_sync_service_spec.rb'
+ - 'ee/spec/services/geo/job_artifact_deleted_event_store_spec.rb'
+ - 'ee/spec/services/geo/project_housekeeping_service_spec.rb'
+ - 'ee/spec/services/geo/repository_base_sync_service_spec.rb'
+ - 'ee/spec/services/geo/repository_updated_service_spec.rb'
+ - 'ee/spec/services/geo/repository_verification_primary_service_spec.rb'
+ - 'ee/spec/services/groups/destroy_service_spec.rb'
+ - 'ee/spec/services/ide/schemas_config_service_spec.rb'
+ - 'ee/spec/services/merge_requests/build_service_spec.rb'
+ - 'ee/spec/services/merge_trains/create_pipeline_service_spec.rb'
+ - 'ee/spec/services/merge_trains/refresh_merge_request_service_spec.rb'
+ - 'ee/spec/services/network_policies/resources_service_spec.rb'
+ - 'ee/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb'
+ - 'ee/spec/services/security/token_revocation_service_spec.rb'
+ - 'ee/spec/services/system_notes/merge_train_service_spec.rb'
+ - 'ee/spec/services/wiki_pages/create_service_spec.rb'
+ - 'ee/spec/services/wiki_pages/destroy_service_spec.rb'
+ - 'ee/spec/services/wiki_pages/update_service_spec.rb'
+ - 'ee/spec/support/shared_examples/models/concerns/elastic/cannot_read_cross_project_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/services/base_sync_service_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/services/geo_event_store_shared_examples.rb'
+ - 'ee/spec/support/shared_examples/services/merge_merge_requests_shared_examples.rb'
+ - 'ee/spec/views/admin/application_settings/_elasticsearch_form.html.haml_spec.rb'
+ - 'ee/spec/views/admin/groups/_form.html.haml_spec.rb'
+ - 'ee/spec/views/layouts/application.html.haml_spec.rb'
+ - 'ee/spec/views/shared/_mirror_update_button.html.haml_spec.rb'
+ - 'ee/spec/workers/ee/ci/build_finished_worker_spec.rb'
+ - 'ee/spec/workers/geo/container_repository_sync_dispatch_worker_spec.rb'
+ - 'ee/spec/workers/geo/design_repository_shard_sync_worker_spec.rb'
+ - 'ee/spec/workers/geo/file_download_dispatch_worker_spec.rb'
+ - 'ee/spec/workers/geo/repository_shard_sync_worker_spec.rb'
+ - 'ee/spec/workers/geo/repository_verification/primary/shard_worker_spec.rb'
+ - 'ee/spec/workers/geo/repository_verification/primary/single_worker_spec.rb'
+ - 'ee/spec/workers/geo/repository_verification/secondary/shard_worker_spec.rb'
+ - 'ee/spec/workers/geo/repository_verification/secondary/single_worker_spec.rb'
+ - 'ee/spec/workers/post_receive_spec.rb'
+ - 'ee/spec/workers/store_security_reports_worker_spec.rb'
+ - 'ee/spec/workers/update_max_seats_used_for_gitlab_com_subscriptions_worker_spec.rb'
+ - 'qa/spec/specs/runner_spec.rb'
+ - 'spec/benchmarks/banzai_benchmark.rb'
+ - 'spec/bin/feature_flag_spec.rb'
+ - 'spec/config/settings_spec.rb'
+ - 'spec/controllers/admin/application_settings_controller_spec.rb'
+ - 'spec/controllers/admin/integrations_controller_spec.rb'
+ - 'spec/controllers/concerns/page_limiter_spec.rb'
+ - 'spec/controllers/concerns/send_file_upload_spec.rb'
+ - 'spec/controllers/concerns/spammable_actions/akismet_mark_as_spam_action_spec.rb'
+ - 'spec/controllers/concerns/spammable_actions/captcha_check/html_format_actions_support_spec.rb'
+ - 'spec/controllers/concerns/spammable_actions/captcha_check/json_format_actions_support_spec.rb'
+ - 'spec/controllers/concerns/spammable_actions/captcha_check/rest_api_actions_support_spec.rb'
+ - 'spec/controllers/groups_controller_spec.rb'
+ - 'spec/controllers/projects/environments_controller_spec.rb'
+ - 'spec/controllers/projects/issues_controller_spec.rb'
+ - 'spec/controllers/projects/jobs_controller_spec.rb'
+ - 'spec/controllers/projects/merge_requests/creations_controller_spec.rb'
+ - 'spec/controllers/projects/merge_requests_controller_spec.rb'
+ - 'spec/controllers/projects/service_desk_controller_spec.rb'
+ - 'spec/controllers/projects_controller_spec.rb'
+ - 'spec/controllers/search_controller_spec.rb'
+ - 'spec/features/groups/clusters/user_spec.rb'
+ - 'spec/features/groups/container_registry_spec.rb'
+ - 'spec/features/markdown/markdown_spec.rb'
+ - 'spec/features/projects/clusters/gcp_spec.rb'
+ - 'spec/features/projects/clusters/user_spec.rb'
+ - 'spec/features/projects/container_registry_spec.rb'
+ - 'spec/features/projects/environments/environment_spec.rb'
+ - 'spec/features/projects/pages/user_edits_lets_encrypt_settings_spec.rb'
+ - 'spec/features/projects/settings/service_desk_setting_spec.rb'
+ - 'spec/finders/events_finder_spec.rb'
+ - 'spec/finders/projects/groups_finder_spec.rb'
+ - 'spec/finders/snippets_finder_spec.rb'
+ - 'spec/finders/user_recent_events_finder_spec.rb'
+ - 'spec/graphql/mutations/environments/canary_ingress/update_spec.rb'
+ - 'spec/graphql/types/project_type_spec.rb'
+ - 'spec/helpers/auth_helper_spec.rb'
+ - 'spec/helpers/broadcast_messages_helper_spec.rb'
+ - 'spec/helpers/dashboard_helper_spec.rb'
+ - 'spec/helpers/diff_helper_spec.rb'
+ - 'spec/helpers/explore_helper_spec.rb'
+ - 'spec/helpers/groups_helper_spec.rb'
+ - 'spec/helpers/issues_helper_spec.rb'
+ - 'spec/helpers/nav/new_dropdown_helper_spec.rb'
+ - 'spec/helpers/nav/top_nav_helper_spec.rb'
+ - 'spec/helpers/nav_helper_spec.rb'
+ - 'spec/helpers/operations_helper_spec.rb'
+ - 'spec/helpers/projects_helper_spec.rb'
+ - 'spec/helpers/users/callouts_helper_spec.rb'
+ - 'spec/helpers/users_helper_spec.rb'
+ - 'spec/helpers/visibility_level_helper_spec.rb'
+ - 'spec/lib/backup/files_spec.rb'
+ - 'spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb'
+ - 'spec/lib/banzai/filter/references/external_issue_reference_filter_spec.rb'
+ - 'spec/lib/banzai/reference_parser/issue_parser_spec.rb'
+ - 'spec/lib/banzai/reference_redactor_spec.rb'
+ - 'spec/lib/file_size_validator_spec.rb'
+ - 'spec/lib/gitlab/auth/o_auth/user_spec.rb'
+ - 'spec/lib/gitlab/auth/saml/user_spec.rb'
+ - 'spec/lib/gitlab/auth_spec.rb'
+ - 'spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb'
+ - 'spec/lib/gitlab/batch_pop_queueing_spec.rb'
+ - 'spec/lib/gitlab/bitbucket_import/importer_spec.rb'
+ - 'spec/lib/gitlab/ci/build/policy/changes_spec.rb'
+ - 'spec/lib/gitlab/ci/config/external/mapper_spec.rb'
+ - 'spec/lib/gitlab/ci/config/external/processor_spec.rb'
+ - 'spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb'
+ - 'spec/lib/gitlab/ci/status/build/failed_spec.rb'
+ - 'spec/lib/gitlab/ci/trace/remote_checksum_spec.rb'
+ - 'spec/lib/gitlab/contributions_calendar_spec.rb'
+ - 'spec/lib/gitlab/daemon_spec.rb'
+ - 'spec/lib/gitlab/database_importers/instance_administrators/create_group_spec.rb'
+ - 'spec/lib/gitlab/diff/file_spec.rb'
+ - 'spec/lib/gitlab/email/message/build_ios_app_guide_spec.rb'
+ - 'spec/lib/gitlab/email/message/in_product_marketing/helper_spec.rb'
+ - 'spec/lib/gitlab/error_tracking_spec.rb'
+ - 'spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb'
+ - 'spec/lib/gitlab/exclusive_lease_helpers_spec.rb'
+ - 'spec/lib/gitlab/experimentation/controller_concern_spec.rb'
+ - 'spec/lib/gitlab/external_authorization_spec.rb'
+ - 'spec/lib/gitlab/git/blob_spec.rb'
+ - 'spec/lib/gitlab/git_access_spec.rb'
+ - 'spec/lib/gitlab/git_access_wiki_spec.rb'
+ - 'spec/lib/gitlab/gitaly_client/call_spec.rb'
+ - 'spec/lib/gitlab/health_checks/puma_check_spec.rb'
+ - 'spec/lib/gitlab/import_export/config_spec.rb'
+ - 'spec/lib/gitlab/instrumentation/redis_base_spec.rb'
+ - 'spec/lib/gitlab/legacy_github_import/importer_spec.rb'
+ - 'spec/lib/gitlab/memory/instrumentation_spec.rb'
+ - 'spec/lib/gitlab/metrics/rails_slis_spec.rb'
+ - 'spec/lib/gitlab/metrics/system_spec.rb'
+ - 'spec/lib/gitlab/middleware/read_only_spec.rb'
+ - 'spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb'
+ - 'spec/lib/gitlab/prometheus_client_spec.rb'
+ - 'spec/lib/gitlab/redis/cache_spec.rb'
+ - 'spec/lib/gitlab/redis/shared_state_spec.rb'
+ - 'spec/lib/gitlab/relative_positioning/range_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_daemon/memory_killer_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb'
+ - 'spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executed_spec.rb'
+ - 'spec/lib/gitlab/workhorse_spec.rb'
+ - 'spec/lib/safe_zip/entry_spec.rb'
+ - 'spec/lib/system_check/simple_executor_spec.rb'
+ - 'spec/models/ability_spec.rb'
+ - 'spec/models/blob_spec.rb'
+ - 'spec/models/ci/build_spec.rb'
+ - 'spec/models/ci/pipeline_spec.rb'
+ - 'spec/models/ci/processable_spec.rb'
+ - 'spec/models/concerns/deprecated_assignee_spec.rb'
+ - 'spec/models/concerns/discussion_on_diff_spec.rb'
+ - 'spec/models/diff_note_spec.rb'
+ - 'spec/models/diff_viewer/image_spec.rb'
+ - 'spec/models/environment_spec.rb'
+ - 'spec/models/integrations/jira_spec.rb'
+ - 'spec/models/internal_id_spec.rb'
+ - 'spec/models/issue_spec.rb'
+ - 'spec/models/merge_request_spec.rb'
+ - 'spec/models/pages_domain_spec.rb'
+ - 'spec/models/project_spec.rb'
+ - 'spec/models/project_statistics_spec.rb'
+ - 'spec/models/snippet_statistics_spec.rb'
+ - 'spec/models/user_spec.rb'
+ - 'spec/models/wiki_page_spec.rb'
+ - 'spec/policies/issue_policy_spec.rb'
+ - 'spec/presenters/ci/pipeline_presenter_spec.rb'
+ - 'spec/presenters/commit_status_presenter_spec.rb'
+ - 'spec/presenters/merge_request_presenter_spec.rb'
+ - 'spec/requests/api/ci/jobs_spec.rb'
+ - 'spec/requests/api/graphql/read_only_spec.rb'
+ - 'spec/requests/api/groups_spec.rb'
+ - 'spec/requests/api/internal/base_spec.rb'
+ - 'spec/requests/api/merge_requests_spec.rb'
+ - 'spec/requests/api/project_container_repositories_spec.rb'
+ - 'spec/requests/git_http_spec.rb'
+ - 'spec/requests/health_controller_spec.rb'
+ - 'spec/requests/lfs_http_spec.rb'
+ - 'spec/requests/users_controller_spec.rb'
+ - 'spec/serializers/diff_file_entity_spec.rb'
+ - 'spec/serializers/merge_request_poll_cached_widget_entity_spec.rb'
+ - 'spec/services/application_settings/update_service_spec.rb'
+ - 'spec/services/auto_merge/base_service_spec.rb'
+ - 'spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb'
+ - 'spec/services/captcha/captcha_verification_service_spec.rb'
+ - 'spec/services/ci/archive_trace_service_spec.rb'
+ - 'spec/services/ci/create_pipeline_service/logger_spec.rb'
+ - 'spec/services/ci/list_config_variables_service_spec.rb'
+ - 'spec/services/ci/pipeline_artifacts/destroy_all_expired_service_spec.rb'
+ - 'spec/services/ci/pipeline_trigger_service_spec.rb'
+ - 'spec/services/ci/process_build_service_spec.rb'
+ - 'spec/services/ci/register_job_service_spec.rb'
+ - 'spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb'
+ - 'spec/services/environments/auto_stop_service_spec.rb'
+ - 'spec/services/environments/canary_ingress/update_service_spec.rb'
+ - 'spec/services/environments/reset_auto_stop_service_spec.rb'
+ - 'spec/services/git/branch_hooks_service_spec.rb'
+ - 'spec/services/git/branch_push_service_spec.rb'
+ - 'spec/services/git/process_ref_changes_service_spec.rb'
+ - 'spec/services/groups/create_service_spec.rb'
+ - 'spec/services/groups/nested_create_service_spec.rb'
+ - 'spec/services/merge_requests/merge_orchestration_service_spec.rb'
+ - 'spec/services/merge_requests/merge_service_spec.rb'
+ - 'spec/services/merge_requests/merge_to_ref_service_spec.rb'
+ - 'spec/services/merge_requests/mergeability_check_service_spec.rb'
+ - 'spec/services/merge_requests/update_service_spec.rb'
+ - 'spec/services/notes/create_service_spec.rb'
+ - 'spec/services/notes/destroy_service_spec.rb'
+ - 'spec/services/notification_service_spec.rb'
+ - 'spec/services/projects/after_rename_service_spec.rb'
+ - 'spec/services/projects/apple_target_platform_detector_service_spec.rb'
+ - 'spec/services/projects/create_service_spec.rb'
+ - 'spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb'
+ - 'spec/services/projects/hashed_storage/migrate_repository_service_spec.rb'
+ - 'spec/services/projects/hashed_storage/rollback_attachments_service_spec.rb'
+ - 'spec/services/projects/hashed_storage/rollback_repository_service_spec.rb'
+ - 'spec/services/projects/in_product_marketing_campaign_emails_service_spec.rb'
+ - 'spec/services/projects/update_remote_mirror_service_spec.rb'
+ - 'spec/services/projects/update_service_spec.rb'
+ - 'spec/services/static_site_editor/config_service_spec.rb'
+ - 'spec/services/suggestions/apply_service_spec.rb'
+ - 'spec/services/suggestions/create_service_spec.rb'
+ - 'spec/services/verify_pages_domain_service_spec.rb'
+ - 'spec/support/redis/redis_shared_examples.rb'
+ - 'spec/support/shared_contexts/services/projects/container_repository/delete_tags_service_shared_context.rb'
+ - 'spec/support/shared_examples/features/container_registry_shared_examples.rb'
+ - 'spec/support/shared_examples/finders/snippet_visibility_shared_examples.rb'
+ - 'spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb'
+ - 'spec/support/shared_examples/lib/gitlab/sidekiq_middleware/strategy_shared_examples.rb'
+ - 'spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb'
+ - 'spec/support/shared_examples/models/wiki_shared_examples.rb'
+ - 'spec/support/shared_examples/path_extraction_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb'
+ - 'spec/support/shared_examples/services/boards/boards_create_service_shared_examples.rb'
+ - 'spec/support/shared_examples/services/boards/create_service_shared_examples.rb'
+ - 'spec/support/shared_examples/uploaders/object_storage_shared_examples.rb'
+ - 'spec/support/shared_examples/workers/concerns/git_garbage_collect_methods_shared_examples.rb'
+ - 'spec/uploaders/file_mover_spec.rb'
+ - 'spec/uploaders/gitlab_uploader_spec.rb'
+ - 'spec/uploaders/object_storage_spec.rb'
+ - 'spec/uploaders/workers/object_storage/background_move_worker_spec.rb'
+ - 'spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb'
+ - 'spec/views/admin/application_settings/_eks.html.haml_spec.rb'
+ - 'spec/views/admin/application_settings/_package_registry.html.haml_spec.rb'
+ - 'spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb'
+ - 'spec/views/shared/milestones/_top.html.haml_spec.rb'
+ - 'spec/views/shared/projects/_project.html.haml_spec.rb'
+ - 'spec/views/shared/snippets/_snippet.html.haml_spec.rb'
+ - 'spec/workers/environments/auto_delete_cron_worker_spec.rb'
+ - 'spec/workers/projects/git_garbage_collect_worker_spec.rb'
diff --git a/.rubocop_todo/style/format_string.yml b/.rubocop_todo/style/format_string.yml
new file mode 100644
index 00000000000..82ece4dd3bd
--- /dev/null
+++ b/.rubocop_todo/style/format_string.yml
@@ -0,0 +1,360 @@
+---
+# Cop supports --auto-correct.
+Style/FormatString:
+ # Offense count: 769
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/components/diffs/overflow_warning_component.rb'
+ - 'app/controllers/admin/application_settings_controller.rb'
+ - 'app/controllers/admin/groups_controller.rb'
+ - 'app/controllers/admin/impersonation_tokens_controller.rb'
+ - 'app/controllers/admin/projects_controller.rb'
+ - 'app/controllers/admin/spam_logs_controller.rb'
+ - 'app/controllers/admin/topics_controller.rb'
+ - 'app/controllers/admin/users_controller.rb'
+ - 'app/controllers/concerns/access_tokens_actions.rb'
+ - 'app/controllers/concerns/confirm_email_warning.rb'
+ - 'app/controllers/concerns/enforces_two_factor_authentication.rb'
+ - 'app/controllers/concerns/integrations/actions.rb'
+ - 'app/controllers/concerns/integrations/hooks_execution.rb'
+ - 'app/controllers/concerns/membership_actions.rb'
+ - 'app/controllers/concerns/redirects_for_missing_path_on_tree.rb'
+ - 'app/controllers/concerns/spammable_actions/akismet_mark_as_spam_action.rb'
+ - 'app/controllers/groups/settings/ci_cd_controller.rb'
+ - 'app/controllers/import/bitbucket_server_controller.rb'
+ - 'app/controllers/import/bulk_imports_controller.rb'
+ - 'app/controllers/import/fogbugz_controller.rb'
+ - 'app/controllers/import/gitea_controller.rb'
+ - 'app/controllers/import/github_controller.rb'
+ - 'app/controllers/import/gitlab_groups_controller.rb'
+ - 'app/controllers/import/gitlab_projects_controller.rb'
+ - 'app/controllers/invites_controller.rb'
+ - 'app/controllers/jwt_controller.rb'
+ - 'app/controllers/omniauth_callbacks_controller.rb'
+ - 'app/controllers/profiles/chat_names_controller.rb'
+ - 'app/controllers/profiles/emails_controller.rb'
+ - 'app/controllers/profiles/preferences_controller.rb'
+ - 'app/controllers/profiles/two_factor_auths_controller.rb'
+ - 'app/controllers/profiles_controller.rb'
+ - 'app/controllers/projects/issues_controller.rb'
+ - 'app/controllers/projects/merge_requests_controller.rb'
+ - 'app/controllers/projects/performance_monitoring/dashboards_controller.rb'
+ - 'app/controllers/projects/pipeline_schedules_controller.rb'
+ - 'app/controllers/projects/services_controller.rb'
+ - 'app/controllers/projects/settings/ci_cd_controller.rb'
+ - 'app/controllers/projects_controller.rb'
+ - 'app/controllers/search_controller.rb'
+ - 'app/controllers/users_controller.rb'
+ - 'app/finders/todos_finder.rb'
+ - 'app/graphql/mutations/release_asset_links/create.rb'
+ - 'app/helpers/auth_helper.rb'
+ - 'app/helpers/blob_helper.rb'
+ - 'app/helpers/button_helper.rb'
+ - 'app/helpers/ci/builds_helper.rb'
+ - 'app/helpers/ci/pipelines_helper.rb'
+ - 'app/helpers/ci/runners_helper.rb'
+ - 'app/helpers/colors_helper.rb'
+ - 'app/helpers/emails_helper.rb'
+ - 'app/helpers/form_helper.rb'
+ - 'app/helpers/groups_helper.rb'
+ - 'app/helpers/import_helper.rb'
+ - 'app/helpers/invite_members_helper.rb'
+ - 'app/helpers/issuables_helper.rb'
+ - 'app/helpers/issues_helper.rb'
+ - 'app/helpers/merge_requests_helper.rb'
+ - 'app/helpers/mirror_helper.rb'
+ - 'app/helpers/preferences_helper.rb'
+ - 'app/helpers/profiles_helper.rb'
+ - 'app/helpers/projects_helper.rb'
+ - 'app/helpers/registrations_helper.rb'
+ - 'app/helpers/reminder_emails_helper.rb'
+ - 'app/helpers/search_helper.rb'
+ - 'app/helpers/ssh_keys_helper.rb'
+ - 'app/helpers/storage_helper.rb'
+ - 'app/helpers/tags_helper.rb'
+ - 'app/helpers/time_helper.rb'
+ - 'app/helpers/timeboxes_helper.rb'
+ - 'app/helpers/tree_helper.rb'
+ - 'app/helpers/users_helper.rb'
+ - 'app/helpers/whats_new_helper.rb'
+ - 'app/helpers/wiki_page_version_helper.rb'
+ - 'app/mailers/emails/members.rb'
+ - 'app/mailers/emails/pages_domains.rb'
+ - 'app/mailers/emails/profile.rb'
+ - 'app/models/application_setting.rb'
+ - 'app/models/application_setting_implementation.rb'
+ - 'app/models/concerns/limitable.rb'
+ - 'app/models/concerns/metric_image_uploading.rb'
+ - 'app/models/concerns/spammable.rb'
+ - 'app/models/concerns/timebox.rb'
+ - 'app/models/concerns/token_authenticatable_strategies/encrypted.rb'
+ - 'app/models/container_expiration_policy.rb'
+ - 'app/models/custom_emoji.rb'
+ - 'app/models/description_version.rb'
+ - 'app/models/design_management/design.rb'
+ - 'app/models/diff_note.rb'
+ - 'app/models/diff_viewer/base.rb'
+ - 'app/models/integrations/asana.rb'
+ - 'app/models/integrations/bamboo.rb'
+ - 'app/models/integrations/bugzilla.rb'
+ - 'app/models/integrations/campfire.rb'
+ - 'app/models/integrations/chat_message/pipeline_message.rb'
+ - 'app/models/integrations/confluence.rb'
+ - 'app/models/integrations/custom_issue_tracker.rb'
+ - 'app/models/integrations/datadog.rb'
+ - 'app/models/integrations/discord.rb'
+ - 'app/models/integrations/emails_on_push.rb'
+ - 'app/models/integrations/ewm.rb'
+ - 'app/models/integrations/external_wiki.rb'
+ - 'app/models/integrations/flowdock.rb'
+ - 'app/models/integrations/hangouts_chat.rb'
+ - 'app/models/integrations/irker.rb'
+ - 'app/models/integrations/jenkins.rb'
+ - 'app/models/integrations/jira.rb'
+ - 'app/models/integrations/mattermost.rb'
+ - 'app/models/integrations/pipelines_email.rb'
+ - 'app/models/integrations/pivotaltracker.rb'
+ - 'app/models/integrations/pushover.rb'
+ - 'app/models/integrations/redmine.rb'
+ - 'app/models/integrations/unify_circuit.rb'
+ - 'app/models/integrations/webex_teams.rb'
+ - 'app/models/integrations/youtrack.rb'
+ - 'app/models/integrations/zentao.rb'
+ - 'app/models/milestone.rb'
+ - 'app/models/pages_domain.rb'
+ - 'app/models/project.rb'
+ - 'app/models/resource_event.rb'
+ - 'app/models/sent_notification.rb'
+ - 'app/models/serverless/domain.rb'
+ - 'app/models/snippet.rb'
+ - 'app/models/user.rb'
+ - 'app/models/wiki.rb'
+ - 'app/models/wiki_page.rb'
+ - 'app/presenters/ci/pipeline_presenter.rb'
+ - 'app/presenters/merge_request_presenter.rb'
+ - 'app/presenters/project_presenter.rb'
+ - 'app/serializers/build_details_entity.rb'
+ - 'app/services/alert_management/alerts/update_service.rb'
+ - 'app/services/boards/lists/base_create_service.rb'
+ - 'app/services/bulk_imports/file_download_service.rb'
+ - 'app/services/clusters/applications/check_progress_service.rb'
+ - 'app/services/clusters/applications/check_uninstall_progress_service.rb'
+ - 'app/services/clusters/applications/install_service.rb'
+ - 'app/services/clusters/applications/patch_service.rb'
+ - 'app/services/clusters/applications/upgrade_service.rb'
+ - 'app/services/clusters/aws/authorize_role_service.rb'
+ - 'app/services/clusters/aws/finalize_creation_service.rb'
+ - 'app/services/clusters/aws/verify_provision_status_service.rb'
+ - 'app/services/clusters/gcp/finalize_creation_service.rb'
+ - 'app/services/clusters/gcp/verify_provision_status_service.rb'
+ - 'app/services/clusters/kubernetes/configure_istio_ingress_service.rb'
+ - 'app/services/concerns/update_repository_storage_methods.rb'
+ - 'app/services/concerns/validates_classification_label.rb'
+ - 'app/services/gravatar_service.rb'
+ - 'app/services/groups/transfer_service.rb'
+ - 'app/services/import/bitbucket_server_service.rb'
+ - 'app/services/import/github_service.rb'
+ - 'app/services/issuable_links/create_service.rb'
+ - 'app/services/issues/clone_service.rb'
+ - 'app/services/issues/close_service.rb'
+ - 'app/services/issues/move_service.rb'
+ - 'app/services/issues/set_crm_contacts_service.rb'
+ - 'app/services/jira/requests/base.rb'
+ - 'app/services/lfs/unlock_file_service.rb'
+ - 'app/services/metrics/dashboard/clone_dashboard_service.rb'
+ - 'app/services/metrics/dashboard/transient_embed_service.rb'
+ - 'app/services/metrics/dashboard/update_dashboard_service.rb'
+ - 'app/services/milestones/promote_service.rb'
+ - 'app/services/personal_access_tokens/revoke_service.rb'
+ - 'app/services/pod_logs/elasticsearch_service.rb'
+ - 'app/services/pod_logs/kubernetes_service.rb'
+ - 'app/services/projects/cleanup_service.rb'
+ - 'app/services/projects/create_from_template_service.rb'
+ - 'app/services/projects/import_service.rb'
+ - 'app/services/system_notes/design_management_service.rb'
+ - 'app/services/users/banned_user_base_service.rb'
+ - 'app/validators/addressable_url_validator.rb'
+ - 'app/validators/any_field_validator.rb'
+ - 'app/validators/array_members_validator.rb'
+ - 'app/validators/import/gitlab_projects/remote_file_validator.rb'
+ - 'app/workers/concerns/project_import_options.rb'
+ - 'app/workers/gitlab/import/stuck_import_job.rb'
+ - 'app/workers/object_storage/migrate_uploads_worker.rb'
+ - 'config/initializers/rack_lineprof.rb'
+ - 'danger/roulette/Dangerfile'
+ - 'ee/app/components/billing/plan_component.rb'
+ - 'ee/app/components/namespaces/preview_free_user_cap_alert_component.rb'
+ - 'ee/app/controllers/admin/elasticsearch_controller.rb'
+ - 'ee/app/controllers/admin/geo/application_controller.rb'
+ - 'ee/app/controllers/admin/geo/projects_controller.rb'
+ - 'ee/app/controllers/admin/licenses_controller.rb'
+ - 'ee/app/controllers/concerns/audit_events/date_range.rb'
+ - 'ee/app/controllers/ee/projects/issues_controller.rb'
+ - 'ee/app/controllers/ee/projects_controller.rb'
+ - 'ee/app/controllers/ee/repositories/git_http_client_controller.rb'
+ - 'ee/app/controllers/ee/repositories/lfs_api_controller.rb'
+ - 'ee/app/controllers/groups/saml_group_links_controller.rb'
+ - 'ee/app/controllers/groups/sso_controller.rb'
+ - 'ee/app/controllers/projects/requirements_management/requirements_controller.rb'
+ - 'ee/app/controllers/subscriptions/groups_controller.rb'
+ - 'ee/app/helpers/admin/emails_helper.rb'
+ - 'ee/app/helpers/billing_plans_helper.rb'
+ - 'ee/app/helpers/ee/application_helper.rb'
+ - 'ee/app/helpers/ee/geo_helper.rb'
+ - 'ee/app/helpers/ee/groups/settings_helper.rb'
+ - 'ee/app/helpers/ee/groups_helper.rb'
+ - 'ee/app/helpers/ee/import_helper.rb'
+ - 'ee/app/helpers/ee/profiles_helper.rb'
+ - 'ee/app/helpers/ee/projects_helper.rb'
+ - 'ee/app/helpers/ee/timeboxes_helper.rb'
+ - 'ee/app/helpers/ee/users/callouts_helper.rb'
+ - 'ee/app/helpers/groups/sso_helper.rb'
+ - 'ee/app/helpers/trial_registrations/reassurances_helper.rb'
+ - 'ee/app/helpers/vulnerabilities_helper.rb'
+ - 'ee/app/mailers/emails/namespace_storage_usage_mailer.rb'
+ - 'ee/app/models/ci/minutes/notification.rb'
+ - 'ee/app/models/dast/profile.rb'
+ - 'ee/app/models/dast/site_profile_secret_variable.rb'
+ - 'ee/app/models/dast_site_profile.rb'
+ - 'ee/app/models/dast_site_validation.rb'
+ - 'ee/app/models/ee/member.rb'
+ - 'ee/app/models/geo/upload_registry.rb'
+ - 'ee/app/models/integrations/github.rb'
+ - 'ee/app/models/namespace_limit.rb'
+ - 'ee/app/models/users_security_dashboard_project.rb'
+ - 'ee/app/services/app_sec/dast/profiles/build_config_service.rb'
+ - 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb'
+ - 'ee/app/services/app_sec/dast/scanner_profiles/destroy_service.rb'
+ - 'ee/app/services/app_sec/dast/scanner_profiles/update_service.rb'
+ - 'ee/app/services/app_sec/dast/site_profiles/destroy_service.rb'
+ - 'ee/app/services/app_sec/dast/site_profiles/update_service.rb'
+ - 'ee/app/services/concerns/incident_management/oncall_rotations/shared_rotation_logic.rb'
+ - 'ee/app/services/dora/aggregate_metrics_service.rb'
+ - 'ee/app/services/ee/projects/create_from_template_service.rb'
+ - 'ee/app/services/incident_management/escalation_policies/base_service.rb'
+ - 'ee/app/services/issues/build_from_vulnerability_service.rb'
+ - 'ee/app/services/merge_requests/create_from_vulnerability_data_service.rb'
+ - 'ee/app/services/namespaces/check_excess_storage_size_service.rb'
+ - 'ee/app/services/namespaces/check_storage_size_service.rb'
+ - 'ee/app/services/network_policies/responses.rb'
+ - 'ee/app/services/security/security_orchestration_policies/policy_configuration_validation_service.rb'
+ - 'ee/app/services/security/security_orchestration_policies/validate_policy_service.rb'
+ - 'ee/app/services/timebox_report_service.rb'
+ - 'ee/app/services/vulnerabilities/destroy_dismissal_feedback_service.rb'
+ - 'ee/app/services/vulnerabilities/dismiss_service.rb'
+ - 'ee/app/services/vulnerabilities/finding_dismiss_service.rb'
+ - 'ee/app/services/vulnerabilities/historical_statistics/adjustment_service.rb'
+ - 'ee/app/services/vulnerabilities/statistics/adjustment_service.rb'
+ - 'ee/app/services/vulnerability_external_issue_links/create_service.rb'
+ - 'ee/lib/audit/details.rb'
+ - 'ee/lib/ee/audit/project_changes_auditor.rb'
+ - 'ee/lib/ee/audit/project_setting_changes_auditor.rb'
+ - 'ee/lib/ee/gitlab/checks/push_rules/branch_check.rb'
+ - 'ee/lib/ee/gitlab/checks/push_rules/commit_check.rb'
+ - 'ee/lib/ee/gitlab/ci/pipeline/chain/validate/security_orchestration_policy.rb'
+ - 'ee/lib/ee/gitlab/quick_actions/epic_actions.rb'
+ - 'ee/lib/ee/gitlab/quick_actions/issue_actions.rb'
+ - 'ee/lib/ee/gitlab/quick_actions/issue_and_merge_request_actions.rb'
+ - 'ee/lib/ee/gitlab/scim/deprovision_service.rb'
+ - 'ee/lib/gitlab/analytics/cycle_analytics/stage_events/issue_label_added.rb'
+ - 'ee/lib/gitlab/analytics/cycle_analytics/stage_events/issue_label_removed.rb'
+ - 'ee/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_label_added.rb'
+ - 'ee/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_label_removed.rb'
+ - 'ee/lib/gitlab/auth/group_saml/response_check.rb'
+ - 'ee/lib/gitlab/expiring_subscription_message.rb'
+ - 'ee/lib/gitlab/geo.rb'
+ - 'ee/lib/gitlab/manual_quarterly_co_term_banner.rb'
+ - 'ee/lib/gitlab/manual_renewal_banner.rb'
+ - 'ee/lib/gitlab/vulnerabilities/container_scanning_vulnerability.rb'
+ - 'ee/lib/tasks/gitlab/elastic.rake'
+ - 'ee/spec/controllers/admin/licenses_controller_spec.rb'
+ - 'ee/spec/controllers/groups/security/policies_controller_spec.rb'
+ - 'ee/spec/features/admin/admin_users_spec.rb'
+ - 'ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb'
+ - 'ee/spec/features/groups/analytics/cycle_analytics/multiple_value_streams_spec.rb'
+ - 'lib/api/helpers/packages/conan/api_helpers.rb'
+ - 'lib/bulk_imports/network_error.rb'
+ - 'lib/bulk_imports/users_mapper.rb'
+ - 'lib/flowdock/git/builder.rb'
+ - 'lib/gitlab/bitbucket_server_import/importer.rb'
+ - 'lib/gitlab/checks/push_file_count_check.rb'
+ - 'lib/gitlab/ci/ansi2json/line.rb'
+ - 'lib/gitlab/ci/badge/coverage/template.rb'
+ - 'lib/gitlab/ci/config/entry/tags.rb'
+ - 'lib/gitlab/ci/status/build/waiting_for_approval.rb'
+ - 'lib/gitlab/config_checker/external_database_checker.rb'
+ - 'lib/gitlab/config_checker/puma_rugged_checker.rb'
+ - 'lib/gitlab/console.rb'
+ - 'lib/gitlab/database/async_indexes/index_creator.rb'
+ - 'lib/gitlab/database/background_migration/batched_migration.rb'
+ - 'lib/gitlab/database/migration_helpers.rb'
+ - 'lib/gitlab/database/partitioning/single_numeric_list_partition.rb'
+ - 'lib/gitlab/database/partitioning/time_partition.rb'
+ - 'lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb'
+ - 'lib/gitlab/database/postgres_hll/batch_distinct_counter.rb'
+ - 'lib/gitlab/database/reindexing/reindex_concurrently.rb'
+ - 'lib/gitlab/database_importers/instance_administrators/create_group.rb'
+ - 'lib/gitlab/database_importers/self_monitoring/project/create_service.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/base.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/create.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/helper.rb'
+ - 'lib/gitlab/email/message/in_product_marketing/verify.rb'
+ - 'lib/gitlab/exceptions_app.rb'
+ - 'lib/gitlab/github_import/importer/pull_request_merged_by_importer.rb'
+ - 'lib/gitlab/github_import/issuable_finder.rb'
+ - 'lib/gitlab/github_import/label_finder.rb'
+ - 'lib/gitlab/github_import/milestone_finder.rb'
+ - 'lib/gitlab/github_import/object_counter.rb'
+ - 'lib/gitlab/github_import/page_counter.rb'
+ - 'lib/gitlab/github_import/parallel_scheduling.rb'
+ - 'lib/gitlab/github_import/representation/diff_note.rb'
+ - 'lib/gitlab/import_export/base/relation_factory.rb'
+ - 'lib/gitlab/import_export/error.rb'
+ - 'lib/gitlab/import_export/snippet_repo_restorer.rb'
+ - 'lib/gitlab/jira_import.rb'
+ - 'lib/gitlab/log_timestamp_formatter.rb'
+ - 'lib/gitlab/metrics/dashboard/errors.rb'
+ - 'lib/gitlab/metrics/dashboard/validator/errors.rb'
+ - 'lib/gitlab/quick_actions/command_definition.rb'
+ - 'lib/gitlab/quick_actions/commit_actions.rb'
+ - 'lib/gitlab/quick_actions/issuable_actions.rb'
+ - 'lib/gitlab/quick_actions/issue_actions.rb'
+ - 'lib/gitlab/quick_actions/issue_and_merge_request_actions.rb'
+ - 'lib/gitlab/quick_actions/merge_request_actions.rb'
+ - 'lib/gitlab/quick_actions/relate_actions.rb'
+ - 'lib/gitlab/usage/metrics/name_suggestion.rb'
+ - 'lib/gitlab/version_info.rb'
+ - 'lib/peek/views/detailed_view.rb'
+ - 'lib/tasks/test.rake'
+ - 'qa/qa/service/docker_run/gitlab_runner.rb'
+ - 'spec/controllers/graphql_controller_spec.rb'
+ - 'spec/factories/lfs_objects.rb'
+ - 'spec/features/admin/admin_users_spec.rb'
+ - 'spec/features/groups/import_export/connect_instance_spec.rb'
+ - 'spec/finders/serverless_domain_finder_spec.rb'
+ - 'spec/graphql/resolvers/projects/jira_projects_resolver_spec.rb'
+ - 'spec/helpers/profiles_helper_spec.rb'
+ - 'spec/lib/api/entities/release_spec.rb'
+ - 'spec/lib/gitlab/config_checker/external_database_checker_spec.rb'
+ - 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
+ - 'spec/lib/gitlab/usage/service_ping_report_spec.rb'
+ - 'spec/models/integrations/bamboo_spec.rb'
+ - 'spec/models/integrations/datadog_spec.rb'
+ - 'spec/models/serverless/domain_spec.rb'
+ - 'spec/requests/api/graphql/project/jira_projects_spec.rb'
+ - 'spec/services/clusters/applications/patch_service_spec.rb'
+ - 'spec/services/clusters/applications/upgrade_service_spec.rb'
+ - 'spec/services/groups/import_export/export_service_spec.rb'
+ - 'spec/services/projects/import_export/export_service_spec.rb'
+ - 'spec/support/helpers/javascript_fixtures_helpers.rb'
+ - 'spec/support/shared_contexts/bulk_imports_requests_shared_context.rb'
+ - 'spec/support/shared_examples/features/wiki/user_views_wiki_page_shared_examples.rb'
+ - 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb'
+ - 'spec/support/shared_examples/services/jira/requests/base_shared_examples.rb'
+ - 'spec/support/shared_examples/views/registration_features_prompt_shared_examples.rb'
+ - 'spec/validators/any_field_validator_spec.rb'
+ - 'spec/views/groups/edit.html.haml_spec.rb'
+ - 'spec/views/profiles/keys/_form.html.haml_spec.rb'
+ - 'spec/views/profiles/notifications/show.html.haml_spec.rb'
+ - 'tooling/lib/tooling/find_codeowners.rb'
diff --git a/.rubocop_todo/style/single_argument_dig.yml b/.rubocop_todo/style/single_argument_dig.yml
new file mode 100644
index 00000000000..860183426e9
--- /dev/null
+++ b/.rubocop_todo/style/single_argument_dig.yml
@@ -0,0 +1,64 @@
+---
+# Cop supports --auto-correct.
+Style/SingleArgumentDig:
+ # Offense count: 150
+ # Temporarily disabled due to too many offenses
+ Enabled: false
+ Exclude:
+ - 'app/graphql/resolvers/namespace_projects_resolver.rb'
+ - 'app/models/ci/build.rb'
+ - 'app/models/ci/build_report_result.rb'
+ - 'app/models/error_tracking/error_event.rb'
+ - 'app/models/integrations/bamboo.rb'
+ - 'app/serializers/codequality_degradation_entity.rb'
+ - 'app/services/ci/update_build_state_service.rb'
+ - 'ee/app/controllers/subscriptions_controller.rb'
+ - 'ee/app/graphql/ee/resolvers/namespace_projects_resolver.rb'
+ - 'ee/app/graphql/mutations/incident_management/oncall_rotation/base.rb'
+ - 'ee/app/graphql/resolvers/ci/code_coverage_activities_resolver.rb'
+ - 'ee/app/models/vulnerabilities/finding.rb'
+ - 'ee/app/presenters/vulnerability_presenter.rb'
+ - 'ee/app/services/elastic/cluster_reindexing_service.rb'
+ - 'ee/app/workers/concerns/elastic/migration_helper.rb'
+ - 'ee/lib/gitlab/ci/parsers/security/dependency_list.rb'
+ - 'ee/lib/gitlab/subscription_portal/clients/graphql.rb'
+ - 'ee/spec/graphql/mutations/vulnerabilities/create_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/ci/parsers/security/common_spec.rb'
+ - 'ee/spec/lib/ee/gitlab/ci/pipeline/chain/validate/external_spec.rb'
+ - 'ee/spec/lib/gitlab/ci/reports/dependency_list/report_spec.rb'
+ - 'ee/spec/lib/gitlab/elastic/client_spec.rb'
+ - 'ee/spec/models/vulnerabilities/finding_spec.rb'
+ - 'ee/spec/presenters/ci/build_runner_presenter_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/code_coverage_summary_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/dast_scanner_profiles_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/dast_site_profile_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/dast_site_profiles_spec.rb'
+ - 'ee/spec/requests/api/graphql/project/requirements_management/requirements_spec.rb'
+ - 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
+ - 'ee/spec/services/vulnerabilities/manually_create_service_spec.rb'
+ - 'lib/gitlab/ci/badge/coverage/template.rb'
+ - 'lib/gitlab/ci/badge/pipeline/template.rb'
+ - 'lib/gitlab/ci/badge/release/template.rb'
+ - 'lib/gitlab/ci/lint.rb'
+ - 'lib/gitlab/ci/parsers/accessibility/pa11y.rb'
+ - 'lib/gitlab/ci/parsers/security/common.rb'
+ - 'lib/gitlab/ci/reports/codequality_reports.rb'
+ - 'lib/gitlab/ci/reports/security/finding_signature.rb'
+ - 'lib/gitlab/ci/reports/security/scan.rb'
+ - 'lib/gitlab/ci/variables/builder.rb'
+ - 'lib/gitlab/config/entry/simplifiable.rb'
+ - 'lib/gitlab/config/entry/validators.rb'
+ - 'lib/gitlab/database/transaction/observer.rb'
+ - 'lib/gitlab/serverless/service.rb'
+ - 'qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb'
+ - 'spec/controllers/graphql_controller_spec.rb'
+ - 'spec/graphql/types/release_links_type_spec.rb'
+ - 'spec/helpers/projects_helper_spec.rb'
+ - 'spec/lib/gitlab/ci/yaml_processor_spec.rb'
+ - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb'
+ - 'spec/requests/api/graphql/container_repository/container_repository_details_spec.rb'
+ - 'spec/requests/api/graphql/project/container_repositories_spec.rb'
+ - 'spec/requests/api/graphql/project/jira_import_spec.rb'
+ - 'spec/requests/api/graphql/project/terraform/states_spec.rb'
+ - 'spec/services/ci/create_pipeline_service/rules_spec.rb'
+ - 'spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb'
diff --git a/Gemfile b/Gemfile
index 5c231f8178e..1dd8fd325fd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -46,7 +46,6 @@ gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.4'
gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.6.0'
-gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.10'
gem 'omniauth-shibboleth', '~> 1.3.0'
@@ -61,6 +60,7 @@ gem 'jwt', '~> 2.1.0'
# Kerberos authentication. EE-only
gem 'gssapi', group: :kerberos
+gem 'timfel-krb5-auth', '~> 0.8', group: :kerberos
# Spam and anti-bot protection
gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index 5f2e4f18ca1..3344feae54b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -873,11 +873,6 @@ GEM
jwt (>= 2.0)
omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.5)
- omniauth-kerberos (0.3.0)
- omniauth-multipassword
- timfel-krb5-auth (~> 0.8)
- omniauth-multipassword (0.4.2)
- omniauth (~> 1.0)
omniauth-oauth (1.1.0)
oauth
omniauth (~> 1.0)
@@ -1594,7 +1589,6 @@ DEPENDENCIES
omniauth-github (~> 1.4)
omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.6.0)
- omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-salesforce (~> 1.0.5)
omniauth-saml (~> 1.10)
@@ -1690,6 +1684,7 @@ DEPENDENCIES
thin (~> 1.8.0)
thrift (>= 0.14.0)
timecop (~> 0.9.1)
+ timfel-krb5-auth (~> 0.8)
toml-rb (~> 2.0)
truncato (~> 0.7.11)
typhoeus (~> 1.4.0)
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 7ff623a1adc..7af3ad80a39 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -157,7 +157,7 @@ export const contentTop = () => {
() => getOuterHeight('#js-peek'),
() => getOuterHeight('.navbar-gitlab'),
({ desktop }) => {
- const container = document.querySelector('.line-resolve-all-container');
+ const container = document.querySelector('.discussions-counter');
let size = 0;
if (!desktop && container) {
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index 78e3b15913a..ff8ccded83b 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -19,6 +19,7 @@ import invalidUrl from '~/lib/utils/invalid_url';
import { relativePathToAbsolute, getBaseURL, visitUrl, isSafeURL } from '~/lib/utils/url_utility';
import { __, n__ } from '~/locale';
import TrackEventDirective from '~/vue_shared/directives/track_event';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { panelTypes } from '../constants';
import { graphDataToCsv } from '../csv_export';
@@ -57,6 +58,7 @@ export default {
GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
clipboardText: {
type: String,
@@ -141,6 +143,9 @@ export default {
return metrics.some(({ loading }) => loading);
},
logsPathWithTimeRange() {
+ if (!this.glFeatures.monitorLogging) {
+ return null;
+ }
const timeRange = this.zoomedTimeRange || this.timeRange;
if (this.logsPath && this.logsPath !== invalidUrl && timeRange) {
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index a1377415efe..7424c011052 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -31,6 +31,8 @@ export default function initMrNotes() {
const el = document.getElementById('js-vue-discussion-counter');
if (el) {
+ const { blocksMerge } = el.dataset;
+
// eslint-disable-next-line no-new
new Vue({
el,
@@ -40,7 +42,11 @@ export default function initMrNotes() {
},
store,
render(createElement) {
- return createElement('discussion-counter');
+ return createElement('discussion-counter', {
+ props: {
+ blocksMerge: blocksMerge === 'true',
+ },
+ });
},
});
}
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 33819c78c0f..717ebcbe993 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTooltipDirective, GlIcon, GlButton, GlButtonGroup } from '@gitlab/ui';
+import { GlTooltipDirective, GlButton, GlButtonGroup } from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
import { __ } from '~/locale';
import discussionNavigation from '../mixins/discussion_navigation';
@@ -9,46 +9,41 @@ export default {
GlTooltip: GlTooltipDirective,
},
components: {
- GlIcon,
GlButton,
GlButtonGroup,
},
mixins: [discussionNavigation],
+ props: {
+ blocksMerge: {
+ type: Boolean,
+ required: true,
+ },
+ },
computed: {
...mapGetters([
- 'getUserData',
'getNoteableData',
'resolvableDiscussionsCount',
'unresolvedDiscussionsCount',
- 'discussions',
+ 'allResolvableDiscussions',
]),
- isLoggedIn() {
- return this.getUserData.id;
- },
allResolved() {
return this.unresolvedDiscussionsCount === 0;
},
- resolveAllDiscussionsIssuePath() {
- return this.getNoteableData.create_issue_to_resolve_discussions_path;
- },
- toggeableDiscussions() {
- return this.discussions.filter((discussion) => !discussion.individual_note);
- },
allExpanded() {
- return this.toggeableDiscussions.every((discussion) => discussion.expanded);
- },
- lineResolveClass() {
- return this.allResolved ? 'line-resolve-btn is-active' : 'line-resolve-text';
+ return this.allResolvableDiscussions.every((discussion) => discussion.expanded);
},
toggleThreadsLabel() {
return this.allExpanded ? __('Collapse all threads') : __('Expand all threads');
},
+ resolveAllDiscussionsIssuePath() {
+ return this.getNoteableData.create_issue_to_resolve_discussions_path;
+ },
},
methods: {
...mapActions(['setExpandDiscussions']),
handleExpandDiscussions() {
this.setExpandDiscussions({
- discussionIds: this.toggeableDiscussions.map((discussion) => discussion.id),
+ discussionIds: this.allResolvableDiscussions.map((discussion) => discussion.id),
expanded: !this.allExpanded,
});
},
@@ -60,21 +55,61 @@ export default {
<div
v-if="resolvableDiscussionsCount > 0"
ref="discussionCounter"
- class="line-resolve-all-container full-width-mobile gl-display-flex d-sm-flex"
+ class="gl-display-flex discussions-counter"
>
- <div class="line-resolve-all">
- <span :class="lineResolveClass">
- <template v-if="allResolved">
- <gl-icon name="check-circle-filled" />
- {{ __('All threads resolved') }}
- </template>
- <template v-else>
- {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
- </template>
- </span>
+ <div
+ class="gl-display-flex gl-align-items-center gl-pl-4 gl-rounded-base gl-mr-3"
+ :class="{
+ 'gl-bg-orange-50': blocksMerge,
+ 'gl-bg-gray-50': !blocksMerge,
+ 'gl-pr-4': allResolved,
+ 'gl-pr-2': !allResolved,
+ }"
+ data-testid="discussions-counter-text"
+ >
+ <template v-if="allResolved">
+ {{ __('All threads resolved') }}
+ </template>
+ <template v-else>
+ {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
+ <gl-button-group class="gl-ml-3">
+ <gl-button
+ v-gl-tooltip.hover
+ :title="__('Jump to previous unresolved thread')"
+ :aria-label="__('Jump to previous unresolved thread')"
+ class="discussion-previous-btn gl-rounded-base! gl-px-2!"
+ data-track-action="click_button"
+ data-track-label="mr_previous_unresolved_thread"
+ data-track-property="click_previous_unresolved_thread_top"
+ icon="angle-up"
+ category="tertiary"
+ @click="jumpToPreviousDiscussion"
+ />
+ <gl-button
+ v-gl-tooltip.hover
+ :title="__('Jump to next unresolved thread')"
+ :aria-label="__('Jump to next unresolved thread')"
+ class="discussion-next-btn gl-rounded-base! gl-px-2!"
+ data-track-action="click_button"
+ data-track-label="mr_next_unresolved_thread"
+ data-track-property="click_next_unresolved_thread_top"
+ icon="angle-down"
+ category="tertiary"
+ @click="jumpToNextDiscussion"
+ />
+ </gl-button-group>
+ </template>
</div>
<gl-button-group>
<gl-button
+ v-gl-tooltip
+ :title="toggleThreadsLabel"
+ :aria-label="toggleThreadsLabel"
+ class="toggle-all-discussions-btn"
+ :icon="allExpanded ? 'collapse' : 'expand'"
+ @click="handleExpandDiscussions"
+ />
+ <gl-button
v-if="resolveAllDiscussionsIssuePath && !allResolved"
v-gl-tooltip
:href="resolveAllDiscussionsIssuePath"
@@ -83,26 +118,6 @@ export default {
class="new-issue-for-discussion discussion-create-issue-btn"
icon="issue-new"
/>
- <gl-button
- v-if="isLoggedIn && !allResolved"
- v-gl-tooltip
- :title="__('Jump to next unresolved thread')"
- :aria-label="__('Jump to next unresolved thread')"
- class="discussion-next-btn"
- data-track-action="click_button"
- data-track-label="mr_next_unresolved_thread"
- data-track-property="click_next_unresolved_thread_top"
- icon="comment-next"
- @click="jumpToNextDiscussion"
- />
- <gl-button
- v-gl-tooltip
- :title="toggleThreadsLabel"
- :aria-label="toggleThreadsLabel"
- class="toggle-all-discussions-btn"
- :icon="allExpanded ? 'angle-up' : 'angle-down'"
- @click="handleExpandDiscussions"
- />
</gl-button-group>
</div>
</template>
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 204e704e504..0cfc17a6ae9 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -17,6 +17,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import sidebarTimeTrackingEventHub from '~/sidebar/event_hub';
import TaskList from '~/task_list';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
+import SidebarStore from '~/sidebar/stores/sidebar_store';
import * as constants from '../constants';
import eventHub from '../event_hub';
import * as types from './mutation_types';
@@ -369,7 +370,14 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
}
const processQuickActions = (res) => {
- const { errors: { commands_only: message } = { commands_only: null } } = res;
+ const {
+ errors: { commands_only: commandsOnly, command_names: commandNames } = {
+ commands_only: null,
+ command_names: [],
+ },
+ } = res;
+ let message = commandsOnly;
+
/*
The following reply means that quick actions have been successfully applied:
@@ -387,6 +395,13 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
confidentialWidget.setConfidentiality();
}
+ const commands = ['approve', 'merge', 'assign_reviewer', 'assign'];
+ const commandUpdatesAttentionRequest = commandNames[0].some((c) => commands.includes(c));
+
+ if (commandUpdatesAttentionRequest && SidebarStore.singleton.currentUserHasAttention) {
+ message = sprintf(__('%{message}. Your attention request was removed.'), { message });
+ }
+
$('.js-gfm-input').trigger('clear-commands-cache.atwho');
createFlash({
diff --git a/app/assets/javascripts/pipelines/pipeline_tabs.js b/app/assets/javascripts/pipelines/pipeline_tabs.js
index a56ac9d86ab..530917f0402 100644
--- a/app/assets/javascripts/pipelines/pipeline_tabs.js
+++ b/app/assets/javascripts/pipelines/pipeline_tabs.js
@@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo';
import PipelineTabs from 'ee_else_ce/pipelines/components/pipeline_tabs.vue';
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
import { TAB_QUERY_PARAM } from '~/pipelines/constants';
+import { parseBoolean } from '~/lib/utils/common_utils';
import { getPipelineDefaultTab, reportToSentry } from './utils';
Vue.use(VueApollo);
@@ -19,6 +20,9 @@ const createPipelineTabs = (selector, apolloProvider) => {
downloadablePathForReportType,
exposeSecurityDashboard,
exposeLicenseScanningData,
+ graphqlResourceEtag,
+ pipelineIid,
+ pipelineProjectPath,
} = dataset;
const defaultTabValue = getPipelineDefaultTab(window.location.href);
@@ -37,12 +41,15 @@ const createPipelineTabs = (selector, apolloProvider) => {
},
apolloProvider,
provide: {
- canGenerateCodequalityReports: JSON.parse(canGenerateCodequalityReports),
+ canGenerateCodequalityReports: parseBoolean(canGenerateCodequalityReports),
codequalityReportDownloadPath,
defaultTabValue,
downloadablePathForReportType,
- exposeSecurityDashboard: JSON.parse(exposeSecurityDashboard),
- exposeLicenseScanningData: JSON.parse(exposeLicenseScanningData),
+ exposeSecurityDashboard: parseBoolean(exposeSecurityDashboard),
+ exposeLicenseScanningData: parseBoolean(exposeLicenseScanningData),
+ graphqlResourceEtag,
+ pipelineIid,
+ pipelineProjectPath,
},
errorCaptured(err, _vm, info) {
reportToSentry('pipeline_tabs', `error: ${err}, info: ${info}`);
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index 847ef7fec13..3729bd4c601 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -8,7 +8,7 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { redirectTo, getLocationHash } from '~/lib/utils/url_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import CodeIntelligence from '~/code_navigation/components/app.vue';
@@ -197,11 +197,20 @@ export default {
this.legacyRichViewer = html;
}
+ this.scrollToHash();
this.isBinary = binary;
this.isLoadingLegacyViewer = false;
})
.catch(() => this.displayError());
},
+ scrollToHash() {
+ const hash = getLocationHash();
+ if (hash) {
+ // Ensures the browser's native scroll to hash is triggered for async content
+ window.location.hash = '';
+ window.location.hash = hash;
+ }
+ },
displayError() {
createFlash({ message: __('An error occurred while loading the file. Please try again.') });
},
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index 75a1d00fe19..7df901577b8 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -40,6 +40,7 @@ export default class SidebarMediator {
const data = { assignee_ids: assignees };
try {
+ const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('assignees', res.data.assignees);
@@ -48,6 +49,10 @@ export default class SidebarMediator {
this.store.overwrite('reviewers', res.data.reviewers);
}
+ if (currentUserHasAttention && this.store.isAddingAssignee) {
+ toast(__('Assigned user(s). Your attention request was removed.'));
+ }
+
return Promise.resolve(res);
} catch (e) {
return Promise.reject(e);
@@ -63,11 +68,16 @@ export default class SidebarMediator {
const data = { reviewer_ids: reviewers };
try {
+ const { currentUserHasAttention } = this.store;
const res = await this.service.update(field, data);
this.store.overwrite('reviewers', res.data.reviewers);
this.store.overwrite('assignees', res.data.assignees);
+ if (currentUserHasAttention && this.store.isAddingAssignee) {
+ toast(__('Requested review. Your attention request was removed.'));
+ }
+
return Promise.resolve(res);
} catch (e) {
return Promise.reject();
@@ -120,12 +130,22 @@ export default class SidebarMediator {
);
} else {
const currentUserId = gon.current_user_id;
+ const { currentUserHasAttention } = this.store;
if (currentUserId !== user.id) {
this.removeCurrentUserAttentionRequested();
}
- toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));
+ toast(
+ currentUserHasAttention && currentUserId !== user.id
+ ? sprintf(
+ __(
+ 'Requested attention from @%{username}. Your own attention request was removed.',
+ ),
+ { username: user.username },
+ )
+ : sprintf(__('Requested attention from @%{username}'), { username: user.username }),
+ );
}
this.store.updateReviewer(user.id, 'attention_requested');
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 2caa6f4f0a0..ca85ee7fd94 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -18,7 +18,9 @@ export default class SidebarStore {
this.humanTimeSpent = '';
this.timeTrackingLimitToHours = timeTrackingLimitToHours;
this.assignees = [];
+ this.addingAssignees = [];
this.reviewers = [];
+ this.addingReviewers = [];
this.isFetching = {
assignees: true,
reviewers: true,
@@ -32,6 +34,7 @@ export default class SidebarStore {
this.subscribeDisabledDescription = '';
this.subscribed = null;
this.changing = false;
+ this.issuableType = options.issuableType;
SidebarStore.singleton = this;
}
@@ -73,12 +76,20 @@ export default class SidebarStore {
if (!this.findAssignee(assignee)) {
this.changing = true;
this.assignees.push(assignee);
+
+ if (assignee.id !== this.currentUser.id) {
+ this.addingAssignees.push(assignee.id);
+ }
}
}
addReviewer(reviewer) {
if (!this.findReviewer(reviewer)) {
this.reviewers.push(reviewer);
+
+ if (reviewer.id !== this.currentUser.id) {
+ this.addingReviewers.push(reviewer.id);
+ }
}
}
@@ -114,12 +125,14 @@ export default class SidebarStore {
if (assignee) {
this.changing = true;
this.assignees = this.assignees.filter(({ id }) => id !== assignee.id);
+ this.addingAssignees = this.addingAssignees.filter(({ id }) => id !== assignee.id);
}
}
removeReviewer(reviewer) {
if (reviewer) {
this.reviewers = this.reviewers.filter(({ id }) => id !== reviewer.id);
+ this.addingReviewers = this.addingReviewers.filter(({ id }) => id !== reviewer.id);
}
}
@@ -147,4 +160,26 @@ export default class SidebarStore {
setMoveToProjectId(moveToProjectId) {
this.moveToProjectId = moveToProjectId;
}
+
+ get currentUserHasAttention() {
+ if (!window.gon?.features?.mrAttentionRequests || !this.isMergeRequest) return false;
+
+ const currentUserId = this.currentUser.id;
+ const currentUserReviewer = this.findReviewer({ id: currentUserId });
+ const currentUserAssignee = this.findAssignee({ id: currentUserId });
+
+ return currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested;
+ }
+
+ get isAddingAssignee() {
+ return this.addingAssignees.length > 0;
+ }
+
+ get isAddingReviewer() {
+ return this.addingReviewers.length > 0;
+ }
+
+ get isMergeRequest() {
+ return this.issuableType === 'merge_request';
+ }
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index d5cd05ff0e1..e7d5e4086bc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -2,8 +2,11 @@
import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
-import { s__ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { s__, __ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
+import showToast from '~/vue_shared/plugins/global_toast';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue';
@@ -21,7 +24,7 @@ export default {
ApprovalsSummaryOptional,
GlButton,
},
- mixins: [approvalsMixin],
+ mixins: [approvalsMixin, glFeatureFlagsMixin()],
props: {
mr: {
type: Object,
@@ -171,6 +174,14 @@ export default {
return serviceFn()
.then((data) => {
this.mr.setApprovals(data);
+
+ if (
+ this.glFeatures.mrAttentionRequests &&
+ SidebarMediator.singleton?.store.currentUserHasAttention
+ ) {
+ showToast(__('Approved. Your attention request was removed.'));
+ }
+
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated');
sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
diff --git a/app/assets/javascripts/vue_shared/components/deployment_instance.vue b/app/assets/javascripts/vue_shared/components/deployment_instance.vue
index 41b783aa011..4aae86fc82b 100644
--- a/app/assets/javascripts/vue_shared/components/deployment_instance.vue
+++ b/app/assets/javascripts/vue_shared/components/deployment_instance.vue
@@ -14,6 +14,7 @@
*/
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -22,7 +23,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
-
+ mixins: [glFeatureFlagsMixin()],
props: {
/**
* Represents the status of the pod. Each state is represented with a different
@@ -75,7 +76,9 @@ export default {
},
computedLogPath() {
- return this.isLink ? mergeUrlParams({ pod_name: this.podName }, this.logsPath) : null;
+ return this.isLink && this.glFeatures.monitorLogging
+ ? mergeUrlParams({ pod_name: this.podName }, this.logsPath)
+ : null;
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
index 88977652556..090bf9493bf 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
@@ -179,7 +179,7 @@ export default {
<gl-button
:disabled="disableCreate"
category="primary"
- variant="success"
+ variant="confirm"
class="gl-display-flex gl-align-items-center"
data-testid="create-button"
@click="createLabel"
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index f95cff012d0..c40871c858f 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -294,8 +294,7 @@ $tabs-holder-z-index: 250;
justify-content: space-between;
@include media-breakpoint-down(xs) {
- .discussion-filter-container,
- .line-resolve-all-container {
+ .discussion-filter-container {
margin-bottom: $gl-padding-4;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 87ffa2a6423..7c5e34c0040 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -838,35 +838,6 @@ $system-note-svg-size: 16px;
}
}
-.line-resolve-all-container {
- > div {
- white-space: nowrap;
- }
-
- .btn-group .btn:first-child {
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- }
-}
-
-.line-resolve-all {
- vertical-align: middle;
- display: inline-block;
- padding: $gl-padding-8 $gl-padding-12;
- background-color: $gray-light;
- border: 1px solid $border-color;
- border-right: 0;
- border-radius: $border-radius-default;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- font-size: $gl-font-size;
- line-height: 1rem;
-
- @include media-breakpoint-down(xs) {
- flex: 1;
- }
-}
-
.line-resolve-btn {
position: relative;
top: 0;
diff --git a/app/controllers/admin/batched_jobs_controller.rb b/app/controllers/admin/batched_jobs_controller.rb
new file mode 100644
index 00000000000..0a00ba13dc8
--- /dev/null
+++ b/app/controllers/admin/batched_jobs_controller.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class Admin::BatchedJobsController < Admin::ApplicationController
+ feature_category :database
+ urgency :low
+
+ around_action :support_multiple_databases
+
+ def show
+ @job = Gitlab::Database::BackgroundMigration::BatchedJob.find(params[:id])
+
+ @transition_logs = @job.batched_job_transition_logs
+ end
+
+ private
+
+ def support_multiple_databases
+ Gitlab::Database::SharedModel.using_connection(base_model.connection) do
+ yield
+ end
+ end
+
+ def base_model
+ @selected_database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME
+
+ Gitlab::Database.database_base_models[@selected_database]
+ end
+end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 8410a8779f6..55b6747fcfb 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -65,7 +65,7 @@ module NotesActions
json.merge!(note_json(@note))
end
- if @note.errors.present? && @note.errors.attribute_names != [:commands_only]
+ if @note.errors.present? && @note.errors.attribute_names != [:commands_only, :command_names]
render json: json, status: :unprocessable_entity
else
render json: json
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index dc5b22e1606..c1c4a110aae 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -9,7 +9,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
after_action :verify_known_sign_in
- protect_from_forgery except: [:kerberos, :saml, :cas3, :failure] + AuthHelper.saml_providers, with: :exception, prepend: true
+ protect_from_forgery except: [:saml, :cas3, :failure] + AuthHelper.saml_providers, with: :exception, prepend: true
feature_category :authentication_and_authorization
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index a100afd3488..1a2c0d64d19 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -24,6 +24,9 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :environment, only: [:show, :edit, :update, :stop, :terminal, :terminal_websocket_authorize, :metrics, :cancel_auto_stop]
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
+ before_action do
+ push_frontend_feature_flag(:monitor_logging, project)
+ end
after_action :expire_etag_cache, only: [:cancel_auto_stop]
feature_category :continuous_delivery
diff --git a/app/controllers/projects/logs_controller.rb b/app/controllers/projects/logs_controller.rb
index b791ca0685b..63d8981ef38 100644
--- a/app/controllers/projects/logs_controller.rb
+++ b/app/controllers/projects/logs_controller.rb
@@ -10,6 +10,8 @@ module Projects
feature_category :logging
def index
+ return render_404 unless Feature.enabled?(:monitor_logging, project)
+
if environment || cluster
render :index
else
diff --git a/app/controllers/projects/metrics_dashboard_controller.rb b/app/controllers/projects/metrics_dashboard_controller.rb
index 3f10749602e..e305b018293 100644
--- a/app/controllers/projects/metrics_dashboard_controller.rb
+++ b/app/controllers/projects/metrics_dashboard_controller.rb
@@ -12,6 +12,7 @@ module Projects
before_action do
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
+ push_frontend_feature_flag(:monitor_logging, project)
end
feature_category :metrics
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 64903c67573..0f7bf893bb2 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -74,7 +74,12 @@ class UserRecentEventsFinder
return Event.none if users.empty?
if Feature.enabled?(:optimized_followed_users_queries, current_user)
- query_builder_params = event_filter.in_operator_query_builder_params(users)
+ array_data = {
+ scope_ids: users,
+ scope_model: User,
+ mapping_column: :author_id
+ }
+ query_builder_params = event_filter.in_operator_query_builder_params(array_data)
Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
.new(**query_builder_params)
diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb
index 185632a49b5..286026bc290 100644
--- a/app/helpers/projects/pipeline_helper.rb
+++ b/app/helpers/projects/pipeline_helper.rb
@@ -7,6 +7,7 @@ module Projects
can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json),
+ pipeline_iid: pipeline.iid,
pipeline_project_path: project.full_path
}
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index b4ad9db815d..43ec02b6537 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -250,6 +250,51 @@ module SortingHelper
sort_options_hash[sort_value]
end
+ def issuable_sort_options(viewing_issues, viewing_merge_requests)
+ options = [
+ { value: sort_value_priority, text: sort_title_priority, href: page_filter_path(sort: sort_value_priority) },
+ { value: sort_value_created_date, text: sort_title_created_date, href: page_filter_path(sort: sort_value_created_date) },
+ { value: sort_value_recently_updated, text: sort_title_recently_updated, href: page_filter_path(sort: sort_value_recently_updated) },
+ { value: sort_value_milestone, text: sort_title_milestone, href: page_filter_path(sort: sort_value_milestone) }
+ ]
+
+ options.concat([due_date_option]) if viewing_issues
+
+ options.concat([popularity_option, label_priority_option])
+ options.concat([merged_option, closed_option]) if viewing_merge_requests
+ options.concat([relative_position_option]) if viewing_issues
+
+ options.concat([title_option])
+ end
+
+ def due_date_option
+ { value: sort_value_due_date, text: sort_title_due_date, href: page_filter_path(sort: sort_value_due_date) }
+ end
+
+ def popularity_option
+ { value: sort_value_popularity, text: sort_title_popularity, href: page_filter_path(sort: sort_value_popularity) }
+ end
+
+ def label_priority_option
+ { value: sort_value_label_priority, text: sort_title_label_priority, href: page_filter_path(sort: sort_value_label_priority) }
+ end
+
+ def merged_option
+ { value: sort_value_merged_date, text: sort_title_merged_date, href: page_filter_path(sort: sort_value_merged_date) }
+ end
+
+ def closed_option
+ { value: sort_value_closed_date, text: sort_title_closed_date, href: page_filter_path(sort: sort_value_closed_date) }
+ end
+
+ def relative_position_option
+ { value: sort_value_relative_position, text: sort_title_relative_position, href: page_filter_path(sort: sort_value_relative_position) }
+ end
+
+ def title_option
+ { value: sort_value_title, text: sort_title_title, href: page_filter_path(sort: sort_value_title) }
+ end
+
def sort_direction_icon(sort_value)
case sort_value
when sort_value_milestone, sort_value_due_date, sort_value_merged_date, sort_value_closed_date, /_asc\z/
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 11e2629e8dd..05d428b8ee6 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -327,11 +327,7 @@ module Ci
after_transition pending: :running do |build|
build.run_after_commit do
- if ::Feature.enabled?(:ci_reduce_persistent_ref_writes, build.project)
- build.ensure_persistent_ref
- else
- build.pipeline.persistent_ref.create
- end
+ build.ensure_persistent_ref
BuildHooksWorker.perform_async(id)
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index dbf094229fe..c10069382f2 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -253,10 +253,6 @@ module Ci
after_transition any => ::Ci::Pipeline.completed_statuses do |pipeline|
pipeline.run_after_commit do
- if ::Feature.disabled?(:ci_reduce_persistent_ref_writes, pipeline.project)
- pipeline.persistent_ref.delete
- end
-
pipeline.all_merge_requests.each do |merge_request|
next unless merge_request.auto_merge_enabled?
@@ -292,9 +288,7 @@ module Ci
after_transition any => ::Ci::Pipeline.stopped_statuses do |pipeline|
pipeline.run_after_commit do
- if ::Feature.enabled?(:ci_reduce_persistent_ref_writes, pipeline.project)
- pipeline.persistent_ref.delete
- end
+ pipeline.persistent_ref.delete
end
end
diff --git a/app/models/event_collection.rb b/app/models/event_collection.rb
index fc093894847..4258027aa56 100644
--- a/app/models/event_collection.rb
+++ b/app/models/event_collection.rb
@@ -8,6 +8,8 @@
class EventCollection
include Gitlab::Utils::StrongMemoize
+ attr_reader :filter
+
# To prevent users from putting too much pressure on the database by cycling
# through thousands of events we put a limit on the number of pages.
MAX_PAGE = 10
@@ -19,7 +21,7 @@ class EventCollection
@projects = projects
@limit = limit
@offset = offset
- @filter = filter
+ @filter = filter || EventFilter.new(EventFilter::ALL)
@groups = groups
end
@@ -44,35 +46,46 @@ class EventCollection
private
def project_events
- in_operator_optimized_relation('project_id', projects)
+ in_operator_optimized_relation('project_id', projects, Project)
end
def group_events
- in_operator_optimized_relation('group_id', groups)
+ in_operator_optimized_relation('group_id', groups, Namespace)
end
def project_and_group_events
- Event.from_union([project_events, group_events]).recent
+ if EventFilter::PROJECT_ONLY_EVENT_TYPES.include?(filter.filter)
+ project_events
+ else
+ Event.from_union([project_events, group_events]).recent
+ end
end
- def in_operator_optimized_relation(parent_column, parents)
- scope = filtered_events
- array_scope = parents.select(:id)
- array_mapping_scope = -> (parent_id_expression) { Event.where(Event.arel_table[parent_column].eq(parent_id_expression)).reorder(id: :desc) }
- finder_query = -> (id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) }
+ def in_operator_optimized_relation(parent_column, parents, parent_model)
+ query_builder_params = if Feature.enabled?(:optimized_project_and_group_activity_queries)
+ array_data = {
+ scope_ids: parents.pluck(:id),
+ scope_model: parent_model,
+ mapping_column: parent_column
+ }
+ filter.in_operator_query_builder_params(array_data)
+ else
+ {
+ scope: filtered_events,
+ array_scope: parents.select(:id),
+ array_mapping_scope: -> (parent_id_expression) { Event.where(Event.arel_table[parent_column].eq(parent_id_expression)).reorder(id: :desc) },
+ finder_query: -> (id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) }
+ }
+ end
Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder
- .new(
- scope: scope,
- array_scope: array_scope,
- array_mapping_scope: array_mapping_scope,
- finder_query: finder_query
- )
+ .new(**query_builder_params)
.execute
+ .limit(@limit + @offset)
end
def filtered_events
- @filter ? @filter.apply_filter(base_relation) : base_relation
+ filter.apply_filter(base_relation)
end
def paginate_events(events)
@@ -99,3 +112,5 @@ class EventCollection
end
end
end
+
+EventCollection.prepend_mod
diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb
index e2d24e74b29..df72a994143 100644
--- a/app/serializers/cluster_entity.rb
+++ b/app/serializers/cluster_entity.rb
@@ -19,7 +19,7 @@ class ClusterEntity < Grape::Entity
Clusters::ClusterPresenter.new(cluster).show_path # rubocop: disable CodeReuse/Presenter
end
- expose :gitlab_managed_apps_logs_path do |cluster|
+ expose :gitlab_managed_apps_logs_path, if: -> (*) { logging_enabled? } do |cluster|
Clusters::ClusterPresenter.new(cluster, current_user: request.current_user).gitlab_managed_apps_logs_path # rubocop: disable CodeReuse/Presenter
end
@@ -27,7 +27,13 @@ class ClusterEntity < Grape::Entity
Clusters::KubernetesErrorEntity.new(cluster)
end
- expose :enable_advanced_logs_querying do |cluster|
+ expose :enable_advanced_logs_querying, if: -> (*) { logging_enabled? } do |cluster|
cluster.elastic_stack_available?
end
+
+ private
+
+ def logging_enabled?
+ Feature.enabled?(:monitor_logging, object.project)
+ end
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 634be365a9d..ac99463bd64 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -103,7 +103,8 @@ class EnvironmentEntity < Grape::Entity
end
def can_read_pod_logs?
- can?(current_user, :read_pod_logs, environment.project)
+ Feature.enabled?(:monitor_logging, environment.project) &&
+ can?(current_user, :read_pod_logs, environment.project)
end
def can_read_deploy_board?
diff --git a/app/services/ci/pipeline_creation/start_pipeline_service.rb b/app/services/ci/pipeline_creation/start_pipeline_service.rb
index bdbb021e4e8..65a045f32dd 100644
--- a/app/services/ci/pipeline_creation/start_pipeline_service.rb
+++ b/app/services/ci/pipeline_creation/start_pipeline_service.rb
@@ -13,9 +13,7 @@ module Ci
##
# Create a persistent ref for the pipeline.
# The pipeline ref is fetched in the jobs and deleted when the pipeline transitions to a finished state.
- if ::Feature.enabled?(:ci_reduce_persistent_ref_writes, pipeline.project)
- pipeline.ensure_persistent_ref
- end
+ pipeline.ensure_persistent_ref
Ci::ProcessPipelineService.new(pipeline).execute
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 30ad549db91..44be254441d 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -265,7 +265,11 @@ module MergeRequests
return if new_assignees.empty?
assignees_map = merge_request.merge_request_assignees_with(new_assignees).to_h do |assignee|
- state = merge_request.find_reviewer(assignee.assignee)&.state || :attention_requested
+ state = if assignee.user_id == current_user&.id
+ :unreviewed
+ else
+ merge_request.find_reviewer(assignee.assignee)&.state || :attention_requested
+ end
[
assignee,
@@ -281,7 +285,11 @@ module MergeRequests
return if new_reviewers.empty?
reviewers_map = merge_request.merge_request_reviewers_with(new_reviewers).to_h do |reviewer|
- state = merge_request.find_assignee(reviewer.reviewer)&.state || :attention_requested
+ state = if reviewer.user_id == current_user&.id
+ :unreviewed
+ else
+ merge_request.find_assignee(reviewer.reviewer)&.state || :attention_requested
+ end
[
reviewer,
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index d32d1c8ca12..ff94f4c1451 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -45,13 +45,13 @@ module Notes
def execute_quick_actions(note)
return yield(false) unless quick_actions_supported?(note)
- content, update_params, message = quick_actions_service.execute(note, quick_action_options)
+ content, update_params, message, command_names = quick_actions_service.execute(note, quick_action_options)
only_commands = content.empty?
note.note = content
yield(only_commands)
- do_commands(note, update_params, message, only_commands)
+ do_commands(note, update_params, message, command_names, only_commands)
end
def quick_actions_supported?(note)
@@ -84,7 +84,7 @@ module Notes
end
end
- def do_commands(note, update_params, message, only_commands)
+ def do_commands(note, update_params, message, command_names, only_commands)
return if quick_actions_service.commands_executed_count.to_i == 0
if update_params.present?
@@ -96,6 +96,7 @@ module Notes
# when #save is called
if only_commands
note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
+ note.errors.add(:command_names, command_names.flatten)
# Allow consumers to detect problems applying commands
note.errors.add(:commands, _('Failed to apply commands.')) unless message.present?
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 30d7093b8a6..4bcb15b2d9c 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -44,7 +44,7 @@ module QuickActions
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
- [content, @updates, execution_messages_for(commands)]
+ [content, @updates, execution_messages_for(commands), command_names(commands)]
end
# Takes a text and interprets the commands that are extracted from it.
@@ -167,6 +167,15 @@ module QuickActions
end.compact
end
+ def command_names(commands)
+ commands.flatten.map do |name|
+ definition = self.class.definition_by_name(name)
+ next unless definition
+
+ name
+ end.compact
+ end
+
def extract_updates(commands)
commands.each do |name, arg|
definition = self.class.definition_by_name(name)
diff --git a/app/views/admin/background_migrations/_job.html.haml b/app/views/admin/background_migrations/_job.html.haml
index 1cf9f9fab2c..e34f73e8b94 100644
--- a/app/views/admin/background_migrations/_job.html.haml
+++ b/app/views/admin/background_migrations/_job.html.haml
@@ -1,6 +1,7 @@
%tr{ role: 'row' }
%td{ role: 'cell', data: { label: _('Id') } }
- = job.id
+ = link_to admin_background_migration_batched_job_path(job.batched_migration, job, params: { database: params[:database] }) do
+ = job.id
%td{ role: 'cell', data: { label: s_('BackgroundMigrations|Started at') } }
= job.started_at
%td{ role: 'cell', data: { label: s_('BackgroundMigrations|Finished at') } }
diff --git a/app/views/admin/background_migrations/_migration.html.haml b/app/views/admin/background_migrations/_migration.html.haml
index 33fcfbd6790..f4906028e39 100644
--- a/app/views/admin/background_migrations/_migration.html.haml
+++ b/app/views/admin/background_migrations/_migration.html.haml
@@ -1,6 +1,6 @@
%tr{ role: 'row' }
%td{ role: 'cell', data: { label: _('Migration') } }
- = link_to admin_background_migration_path(migration) do
+ = link_to admin_background_migration_path(migration, database: params[:database]) do
= migration.job_class_name + ': ' + migration.table_name
%td{ role: 'cell', data: { label: _('Progress') } }
- progress = batched_migration_progress(migration, @successful_rows_counts[migration.id])
diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml
index e3ef2587e5d..c8b195219ec 100644
--- a/app/views/admin/background_migrations/index.html.haml
+++ b/app/views/admin/background_migrations/index.html.haml
@@ -1,4 +1,5 @@
- page_title s_('BackgroundMigrations|Background Migrations')
+- @breadcrumb_link = admin_background_migrations_path(database: params[:database])
.gl-display-flex.gl-sm-flex-direction-column.gl-sm-align-items-flex-end.gl-pb-5.gl-border-b-1.gl-border-b-solid.gl-border-b-gray-100
.gl-flex-grow-1
diff --git a/app/views/admin/background_migrations/show.html.haml b/app/views/admin/background_migrations/show.html.haml
index e8288737b90..7915a9d36dc 100644
--- a/app/views/admin/background_migrations/show.html.haml
+++ b/app/views/admin/background_migrations/show.html.haml
@@ -1,6 +1,7 @@
-- add_to_breadcrumbs _('Background Migrations'), admin_background_migrations_path
-- breadcrumb_title @migration.job_class_name + ': ' + @migration.table_name
-- page_title @migration.job_class_name , _('Background Migrations')
+- add_to_breadcrumbs s_('BackgroundMigrations|Background Migrations'), admin_background_migrations_path(database: params[:database])
+- breadcrumb_title @migration.id
+- page_title @migration.job_class_name , s_('BackgroundMigrations|Background Migrations')
+- @breadcrumb_link = admin_background_migration_path(@migration, database: params[:database])
%h3= @migration.job_class_name + ': ' + @migration.table_name
diff --git a/app/views/admin/batched_jobs/_job.html.haml b/app/views/admin/batched_jobs/_job.html.haml
new file mode 100644
index 00000000000..512f4062ccf
--- /dev/null
+++ b/app/views/admin/batched_jobs/_job.html.haml
@@ -0,0 +1,17 @@
+%tr{ role: 'row' }
+ %td{ role: 'cell', data: { label: _('Id') } }
+ = @job.id
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Min value') } }
+ = @job.min_value
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Max value') } }
+ = @job.max_value
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Batch size') } }
+ = @job.batch_size
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Started at') } }
+ = @job.started_at
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Finished at') } }
+ = @job.finished_at
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Attempts') } }
+ = @job.attempts
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Pause ms') } }
+ = @job.pause_ms
diff --git a/app/views/admin/batched_jobs/_transition_log.html.haml b/app/views/admin/batched_jobs/_transition_log.html.haml
new file mode 100644
index 00000000000..bd81be4679a
--- /dev/null
+++ b/app/views/admin/batched_jobs/_transition_log.html.haml
@@ -0,0 +1,13 @@
+%tr{ role: 'row' }
+ %td{ role: 'cell', data: { label: _('Id') } }
+ = transition_log.id
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Created at') } }
+ = transition_log.created_at
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Previous status') } }
+ = transition_log.previous_status
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Next status') } }
+ = transition_log.next_status
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Exception class') } }
+ = transition_log.exception_class
+ %td{ role: 'cell', data: { label: s_('BatchedJob|Exception message') } }
+ = transition_log.exception_message
diff --git a/app/views/admin/batched_jobs/show.html.haml b/app/views/admin/batched_jobs/show.html.haml
new file mode 100644
index 00000000000..760635413a5
--- /dev/null
+++ b/app/views/admin/batched_jobs/show.html.haml
@@ -0,0 +1,36 @@
+- add_to_breadcrumbs s_('Batched Job|Background Migrations'), admin_background_migrations_path(database: params[:database])
+- add_to_breadcrumbs @job.batched_background_migration_id, admin_background_migration_path(@job.batched_migration, database: params[:database])
+- breadcrumb_title sprintf(s_('Batched Job|Batched Job (Id: %{id})'), { id: @job.id.to_s})
+- page_title @job.id, s_('BatchedJob|Batched Jobs')
+- @breadcrumb_link = admin_background_migration_batched_job_path(@job.batched_migration, @job, database: params[:database])
+
+%h3= sprintf(s_('Batched Job|Batched Job (Id: %{id})'), { id: @job.id.to_s})
+
+%table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' }
+ %thead{ role: 'rowgroup' }
+ %tr{ role: 'row' }
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= _('Id')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Min Value')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Max Value')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Batch size')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Started at')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Finished at')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Attempts')
+ %th.table-th-transparent.border-bottom{ role: 'cell' }= s_('BatchedJob|Pause time (ms)')
+ %tbody{ role: 'rowgroup' }
+ = render partial: 'job', job: @job
+
+- if @transition_logs.any?
+ %h5= s_('BatchedJob|Transition logs:')
+
+ %table.table.b-table.gl-table.b-table-stacked-md{ role: 'table' }
+ %thead{ role: 'rowgroup' }
+ %tr{ role: 'row' }
+ %th{ role: 'cell' }= _('Id')
+ %th{ role: 'cell' }= s_('BatchedJob|Created At')
+ %th{ role: 'cell' }= s_('BatchedJob|Previous Status')
+ %th{ role: 'cell' }= s_('BatchedJob|Next Status')
+ %th{ role: 'cell' }= s_('BatchedJob|Exception Class')
+ %th{ role: 'cell' }= s_('BatchedJob|Exception Message')
+ %tbody{ role: 'rowgroup' }
+ = render partial: 'transition_log', collection: @transition_logs
diff --git a/app/views/clusters/clusters/_integrations.html.haml b/app/views/clusters/clusters/_integrations.html.haml
index c772df2d72c..62ae551fee7 100644
--- a/app/views/clusters/clusters/_integrations.html.haml
+++ b/app/views/clusters/clusters/_integrations.html.haml
@@ -15,13 +15,14 @@
help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
= prometheus_form.submit _('Save changes'), class: 'btn gl-button btn-confirm'
- .sub-section.form-group
- = gitlab_ui_form_for @elastic_stack_integration, as: :integration, namespace: :elastic_stack, url: @cluster.integrations_path, method: :post, html: { class: 'js-cluster-integrations-form' } do |elastic_stack_form|
- = elastic_stack_form.hidden_field :application_type
- .form-group.gl-form-group
- - help_text = s_('ClusterIntegration|Allows GitLab to query a specifically configured in-cluster Elasticsearch for pod logs.')
- - help_link = link_to(_('More information.'), help_page_path("user/clusters/integrations", anchor: "elastic-stack-cluster-integration"), target: '_blank', rel: 'noopener noreferrer')
- = elastic_stack_form.gitlab_ui_checkbox_component :enabled,
- s_('ClusterIntegration|Enable Elastic Stack integration'),
- help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
- = elastic_stack_form.submit _('Save changes'), class: 'btn gl-button btn-confirm'
+ - if Feature.enabled?(:monitor_logging, @project)
+ .sub-section.form-group
+ = gitlab_ui_form_for @elastic_stack_integration, as: :integration, namespace: :elastic_stack, url: @cluster.integrations_path, method: :post, html: { class: 'js-cluster-integrations-form' } do |elastic_stack_form|
+ = elastic_stack_form.hidden_field :application_type
+ .form-group.gl-form-group
+ - help_text = s_('ClusterIntegration|Allows GitLab to query a specifically configured in-cluster Elasticsearch for pod logs.')
+ - help_link = link_to(_('More information.'), help_page_path("user/clusters/integrations", anchor: "elastic-stack-cluster-integration"), target: '_blank', rel: 'noopener noreferrer')
+ = elastic_stack_form.gitlab_ui_checkbox_component :enabled,
+ s_('ClusterIntegration|Enable Elastic Stack integration'),
+ help_text: '%{help_text} %{help_link}'.html_safe % { help_text: help_text, help_link: help_link }
+ = elastic_stack_form.submit _('Save changes'), class: 'btn gl-button btn-confirm'
diff --git a/app/views/notify/merge_request_unmergeable_email.html.haml b/app/views/notify/merge_request_unmergeable_email.html.haml
index 6bcff28985c..f0a5e5d4367 100644
--- a/app/views/notify/merge_request_unmergeable_email.html.haml
+++ b/app/views/notify/merge_request_unmergeable_email.html.haml
@@ -1,2 +1,10 @@
%p
= sprintf(s_('Notify|Merge request %{merge_request} can no longer be merged due to conflict.'), { merge_request: merge_request_reference_link(@merge_request) }).html_safe
+%p
+ = merge_path_description(@merge_request, 'to')
+%p
+ = sprintf(s_('Author: %{author_name}'), { author_name: sanitize_name(@merge_request.author_name) })
+%p
+ = assignees_label(@merge_request)
+%p
+ = reviewers_label(@merge_request)
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index c356b9d5f03..91f999eda36 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -41,7 +41,7 @@
= _("Changes")
= gl_badge_tag @diffs_count, { size: :sm }
.d-flex.flex-wrap.align-items-center.justify-content-lg-end
- #js-vue-discussion-counter
+ #js-vue-discussion-counter{ data: { blocks_merge: @project.only_allow_merge_if_all_discussions_are_resolved?.to_s } }
.tab-content#diff-notes-app
#js-diff-file-finder
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 0d9cb7290ac..9d74f99bb19 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -1,5 +1,5 @@
-- if lookup_context.template_exists?('top', "projects/services/#{integration.to_param}", true)
- = render "projects/services/#{integration.to_param}/top", integration: integration
+- if lookup_context.template_exists?('top', "shared/integrations/#{integration.to_param}", true)
+ = render "shared/integrations/#{integration.to_param}/top", integration: integration
- if integration.activate_disabled_reason.present? && integration.activate_disabled_reason[:trackers].any?
-# When using integration.activate_disabled_reason[:trackers], it's potentially insecure to use the raw records
@@ -17,6 +17,6 @@
= sprite_icon('check', css_class: 'gl-text-green-500')
= render 'shared/integration_settings', integration: integration
-- if lookup_context.template_exists?('show', "projects/services/#{integration.to_param}", true)
+- if lookup_context.template_exists?('show', "shared/integrations/#{integration.to_param}", true)
%hr
- = render "projects/services/#{integration.to_param}/show", integration: integration
+ = render "shared/integrations/#{integration.to_param}/show", integration: integration
diff --git a/app/views/shared/_integration_settings.html.haml b/app/views/shared/_integration_settings.html.haml
index 93606ca0aba..84710b2ecc7 100644
--- a/app/views/shared/_integration_settings.html.haml
+++ b/app/views/shared/_integration_settings.html.haml
@@ -6,8 +6,8 @@
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group, project: @project) }
.js-integration-help-html.gl-display-none
-# All content below will be repositioned in Vue
- - if lookup_context.template_exists?('help', "projects/services/#{integration.to_param}", true)
- = render "projects/services/#{integration.to_param}/help", integration: integration
+ - if lookup_context.template_exists?('help', "shared/integrations/#{integration.to_param}", true)
+ = render "shared/integrations/#{integration.to_param}/help", integration: integration
- elsif integration.help.present?
.info-well
.well-segment
diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/shared/integrations/mattermost_slash_commands/_detailed_help.html.haml
index fec443738c3..fec443738c3 100644
--- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml
+++ b/app/views/shared/integrations/mattermost_slash_commands/_detailed_help.html.haml
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/shared/integrations/mattermost_slash_commands/_help.html.haml
index 993df389fb0..6ce1c65a8dc 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/shared/integrations/mattermost_slash_commands/_help.html.haml
@@ -11,7 +11,7 @@
= s_("MattermostService|After you configure the integration, view your new Mattermost commands by entering")
%kbd.inline /&lt;trigger&gt; help
- if !enabled && integration.project_level?
- = render 'projects/services/mattermost_slash_commands/detailed_help', integration: integration
+ = render 'shared/integrations/mattermost_slash_commands/detailed_help', integration: integration
- if enabled && integration.project_level?
- = render 'projects/services/mattermost_slash_commands/installation_info', integration: integration
+ = render 'shared/integrations/mattermost_slash_commands/installation_info', integration: integration
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/shared/integrations/mattermost_slash_commands/_installation_info.html.haml
index 38adc69dd5e..38adc69dd5e 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/shared/integrations/mattermost_slash_commands/_installation_info.html.haml
diff --git a/app/views/projects/services/prometheus/_custom_metrics.html.haml b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml
index 896249c6163..896249c6163 100644
--- a/app/views/projects/services/prometheus/_custom_metrics.html.haml
+++ b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml
diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/shared/integrations/prometheus/_help.html.haml
index f40d8638845..f40d8638845 100644
--- a/app/views/projects/services/prometheus/_help.html.haml
+++ b/app/views/shared/integrations/prometheus/_help.html.haml
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/shared/integrations/prometheus/_metrics.html.haml
index 8794f3e24da..8ee0ddfa1b1 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/shared/integrations/prometheus/_metrics.html.haml
@@ -1,6 +1,6 @@
- project = local_assigns.fetch(:project)
-= render 'projects/services/prometheus/custom_metrics', project: project, integration: integration
+= render 'shared/integrations/prometheus/custom_metrics', project: project, integration: integration
.col-lg-3
%p
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/shared/integrations/prometheus/_show.html.haml
index c80dc46bdb5..0e133e66794 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/shared/integrations/prometheus/_show.html.haml
@@ -4,4 +4,4 @@
= s_('PrometheusService|Metrics')
.row.gl-mb-3.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
- = render 'projects/services/prometheus/metrics', project: @project, integration: integration
+ = render 'shared/integrations/prometheus/metrics', project: @project, integration: integration
diff --git a/app/views/projects/services/slack/_help.haml b/app/views/shared/integrations/slack/_help.haml
index c5fcd5ca5fe..c5fcd5ca5fe 100644
--- a/app/views/projects/services/slack/_help.haml
+++ b/app/views/shared/integrations/slack/_help.haml
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/shared/integrations/slack_slash_commands/_help.html.haml
index fee0ca15808..fee0ca15808 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/shared/integrations/slack_slash_commands/_help.html.haml
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index f6d7ed6764d..e36c4cd6be0 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -1,26 +1,9 @@
-- sort_value = @sort
-- sort_title = issuable_sort_option_title(sort_value)
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
- viewing_merge_requests = controller.controller_name == 'merge_requests'
+- items = issuable_sort_options(viewing_issues, viewing_merge_requests)
+- selected = issuable_sort_option_overrides[@sort] || @sort
-.dropdown.inline.gl-ml-3.issue-sort-dropdown
+.gl-ml-3
.btn-group{ role: 'group' }
- .btn-group{ role: 'group' }
- %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'gl-button btn btn-default' }
- = sort_title
- = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
- %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
- %li
- = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
- = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
- = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
- = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
- = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
- = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
- = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
- = sortable_item(sort_title_merged_date, page_filter_path(sort: sort_value_merged_date), sort_title) if viewing_merge_requests
- = sortable_item(sort_title_closed_date, page_filter_path(sort: sort_value_closed_date), sort_title) if viewing_merge_requests
- = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues
- = sortable_item(sort_title_title, page_filter_path(sort: sort_value_title), sort_title)
- = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
- = issuable_sort_direction_button(sort_value)
+ = gl_redirect_listbox_tag(items, selected, data: { right: true })
+ = issuable_sort_direction_button(@sort)
diff --git a/config/feature_flags/development/monitor_logging.yml b/config/feature_flags/development/monitor_logging.yml
new file mode 100644
index 00000000000..e9e3f49a3bb
--- /dev/null
+++ b/config/feature_flags/development/monitor_logging.yml
@@ -0,0 +1,8 @@
+---
+name: monitor_logging
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86214
+rollout_issue_url:
+milestone: '15.0'
+type: development
+group: group::respond
+default_enabled: false
diff --git a/config/feature_flags/development/ci_reduce_persistent_ref_writes.yml b/config/feature_flags/development/optimized_project_and_group_activity_queries.yml
index 9329a1f5889..6efb0a3818a 100644
--- a/config/feature_flags/development/ci_reduce_persistent_ref_writes.yml
+++ b/config/feature_flags/development/optimized_project_and_group_activity_queries.yml
@@ -1,8 +1,8 @@
---
-name: ci_reduce_persistent_ref_writes
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83730
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/354837
-milestone: '14.10'
+name: optimized_project_and_group_activity_queries
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/85810
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360458
+milestone: '15.0'
type: development
-group: group::pipeline execution
+group: group::workspace
default_enabled: false
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 2bc050dfa67..8148e92d0e5 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -89,6 +89,8 @@ namespace :admin do
get :instance_review, to: 'instance_review#index'
resources :background_migrations, only: [:index, :show] do
+ resources :batched_jobs, only: [:show]
+
member do
post :pause
post :resume
diff --git a/data/removals/15_0/15-0-dependency-scanning-python-image.yml b/data/removals/15_0/15-0-dependency-scanning-python-image.yml
new file mode 100644
index 00000000000..cbb1e32a211
--- /dev/null
+++ b/data/removals/15_0/15-0-dependency-scanning-python-image.yml
@@ -0,0 +1,11 @@
+- name: "End of support for Python 3.6 in Dependency Scanning"
+ announcement_milestone: "14.8"
+ announcement_date: "2022-02-22"
+ removal_milestone: "15.0"
+ removal_date: "2022-05-22"
+ breaking_change: true
+ reporter: sam.white
+ stage: secure
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351503
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ For those using Dependency Scanning for Python projects, we are removing support for the default `gemnasium-python:2` image which uses Python 3.6, as well as the custom `gemnasium-python:2-python-3.9` image which uses Python 3.9. The new default image as of GitLab 15.0 will be for Python 3.9 as it is a [supported version](https://endoflife.date/python) and 3.6 [is no longer supported](https://endoflife.date/python).
diff --git a/db/post_migrate/20220422121443_add_async_index_for_group_activity_events.rb b/db/post_migrate/20220422121443_add_async_index_for_group_activity_events.rb
new file mode 100644
index 00000000000..92b97203eed
--- /dev/null
+++ b/db/post_migrate/20220422121443_add_async_index_for_group_activity_events.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddAsyncIndexForGroupActivityEvents < Gitlab::Database::Migration[2.0]
+ INDEX_NAME = 'index_events_for_group_activity'
+
+ def up
+ prepare_async_index :events, %I[group_id target_type action id], name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ end
+
+ def down
+ unprepare_async_index :events, %I[group_id target_type action id], name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ end
+end
diff --git a/db/post_migrate/20220425111114_add_async_index_for_project_activity_events.rb b/db/post_migrate/20220425111114_add_async_index_for_project_activity_events.rb
new file mode 100644
index 00000000000..b203ceca976
--- /dev/null
+++ b/db/post_migrate/20220425111114_add_async_index_for_project_activity_events.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddAsyncIndexForProjectActivityEvents < Gitlab::Database::Migration[2.0]
+ INDEX_NAME = 'index_events_for_project_activity'
+
+ def up
+ prepare_async_index :events, %I[project_id target_type action id], name: INDEX_NAME
+ end
+
+ def down
+ unprepare_async_index :events, %I[project_id target_type action id], name: INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20220425111453_add_async_index_to_events_on_group_id_and_id.rb b/db/post_migrate/20220425111453_add_async_index_to_events_on_group_id_and_id.rb
new file mode 100644
index 00000000000..313a31d8964
--- /dev/null
+++ b/db/post_migrate/20220425111453_add_async_index_to_events_on_group_id_and_id.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddAsyncIndexToEventsOnGroupIdAndId < Gitlab::Database::Migration[2.0]
+ INDEX_NAME = 'index_events_on_group_id_and_id'
+
+ def up
+ prepare_async_index :events, %I[group_id id], name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ end
+
+ def down
+ unprepare_async_index :events, %I[group_id id], name: INDEX_NAME, where: 'group_id IS NOT NULL'
+ end
+end
diff --git a/db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb b/db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb
index ef76a097b49..3e0e9b8af0e 100644
--- a/db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb
+++ b/db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb
@@ -1,25 +1,11 @@
# frozen_string_literal: true
class ScheduleExpireOAuthTokens < Gitlab::Database::Migration[2.0]
- MIGRATION = 'ExpireOAuthTokens'
- INTERVAL = 2.minutes.freeze
-
- disable_ddl_transaction!
-
- restrict_gitlab_migration gitlab_schema: :gitlab_main
-
def up
- queue_batched_background_migration(
- MIGRATION,
- :oauth_access_tokens,
- :id,
- job_interval: INTERVAL
- )
+ # reschedulled with db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
end
def down
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(MIGRATION, :oauth_access_tokens, :id, [])
- .delete_all
+ # reschedulled with db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
end
end
diff --git a/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb b/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
new file mode 100644
index 00000000000..0c8a2386583
--- /dev/null
+++ b/db/post_migrate/20220513043344_reschedule_expire_o_auth_tokens.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class RescheduleExpireOAuthTokens < Gitlab::Database::Migration[2.0]
+ MIGRATION = 'ExpireOAuthTokens'
+ INTERVAL = 2.minutes.freeze
+
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ # remove the original migration from db/post_migrate/20220428133724_schedule_expire_o_auth_tokens.rb
+ Gitlab::Database::BackgroundMigration::BatchedMigration
+ .for_configuration(MIGRATION, :oauth_access_tokens, :id, [])
+ .delete_all
+
+ # reschedule
+ queue_batched_background_migration(
+ MIGRATION,
+ :oauth_access_tokens,
+ :id,
+ job_interval: INTERVAL
+ )
+ end
+
+ def down
+ Gitlab::Database::BackgroundMigration::BatchedMigration
+ .for_configuration(MIGRATION, :oauth_access_tokens, :id, [])
+ .delete_all
+ end
+end
diff --git a/db/schema_migrations/20220422121443 b/db/schema_migrations/20220422121443
new file mode 100644
index 00000000000..79a69c6af87
--- /dev/null
+++ b/db/schema_migrations/20220422121443
@@ -0,0 +1 @@
+5febc5341ccfd930c8dbc77ca2b2bbadb351444a616e657f8ce8cd477c73280f \ No newline at end of file
diff --git a/db/schema_migrations/20220425111114 b/db/schema_migrations/20220425111114
new file mode 100644
index 00000000000..ca0b2c6f908
--- /dev/null
+++ b/db/schema_migrations/20220425111114
@@ -0,0 +1 @@
+a9ba21d5f1fcff29b0f14d5bab99dd867ec101667f021914da845e286aabe2a5 \ No newline at end of file
diff --git a/db/schema_migrations/20220425111453 b/db/schema_migrations/20220425111453
new file mode 100644
index 00000000000..45326d41fdd
--- /dev/null
+++ b/db/schema_migrations/20220425111453
@@ -0,0 +1 @@
+30d5d5538f965562f594a78b9430a11ea87ea900216ee9c03df422ea47de8f0c \ No newline at end of file
diff --git a/db/schema_migrations/20220513043344 b/db/schema_migrations/20220513043344
new file mode 100644
index 00000000000..af72eebc3b9
--- /dev/null
+++ b/db/schema_migrations/20220513043344
@@ -0,0 +1 @@
+33928e6cb39e42efae6c8bc12291317a08197c0fe5a1f912aac8972eabc96de7 \ No newline at end of file
diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md
index 61219997816..8d3fec3b19e 100644
--- a/doc/administration/instance_limits.md
+++ b/doc/administration/instance_limits.md
@@ -636,7 +636,8 @@ If the limit value is set to zero, the limit is disabled.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276192) in GitLab 14.1, disabled by default.
> - Enabled by default and [feature flag `ci_jobs_trace_size_limit` removed](https://gitlab.com/gitlab-org/gitlab/-/issues/335259) in GitLab 14.2.
-The job log file size limit is 100 megabytes by default. Any job that exceeds this value is dropped.
+The job log file size limit in GitLab is 100 megabytes by default. Any job that exceeds the
+limit is marked as failed, and dropped by the runner.
You can change the limit in the [GitLab Rails console](operations/rails_console.md#starting-a-rails-console-session).
Update `ci_jobs_trace_size_limit` with the new value in megabytes:
@@ -645,6 +646,11 @@ Update `ci_jobs_trace_size_limit` with the new value in megabytes:
Plan.default.actual_limits.update!(ci_jobs_trace_size_limit: 125)
```
+GitLab Runner also has an
+[`output_limit` setting](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section)
+that configures the maximum log size in a runner. Jobs that exceed the runner limit
+continue to run, but the log is truncated when it hits the limit.
+
### Maximum number of active DAST profile schedules per project
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68551) in GitLab 14.3.
diff --git a/doc/api/personal_access_tokens.md b/doc/api/personal_access_tokens.md
index b51866fe9b1..f3dff4aca2c 100644
--- a/doc/api/personal_access_tokens.md
+++ b/doc/api/personal_access_tokens.md
@@ -72,10 +72,12 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/a
## Revoke a personal access token
+### Revoke a personal access token by ID
+
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216004) in GitLab 13.3.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/270200) from GitLab Ultimate to GitLab Free in 13.6.
-Revoke a personal access token.
+Revoke a personal access token by ID.
```plaintext
DELETE /personal_access_tokens/:id
@@ -92,10 +94,29 @@ Non-administrators can revoke their own tokens. Administrators can revoke tokens
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/personal_access_tokens/<personal_access_token_id>"
```
-### Responses
+#### Responses
+
+- `204: No Content` if successfully revoked.
+- `400: Bad Request` if not revoked successfully.
+
+### Revoke a personal access token using a header
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350240) in GitLab 15.0.
+
+Revokes a personal access token that is passed in using a request header.
+
+```plaintext
+DELETE /personal_access_tokens/self
+```
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/personal_access_tokens/self"
+```
+
+#### Responses
- `204: No Content` if successfully revoked.
-- `400 Bad Request` if not revoked successfully.
+- `400: Bad Request` if not revoked successfully.
## Create a personal access token (administrator only)
diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md
index 05ab4eb3572..e595fea6d96 100644
--- a/doc/development/integrations/index.md
+++ b/doc/development/integrations/index.md
@@ -134,7 +134,7 @@ By default, the integration form provides:
- Checkboxes for each of the trigger events returned from `Integration#configurable_events`.
You can also add help text at the top of the form by either overriding `Integration#help`,
-or providing a template in `app/views/projects/services/$INTEGRATION_NAME/_help.html.haml`.
+or providing a template in `app/views/shared/integrations/$INTEGRATION_NAME/_help.html.haml`.
To add your custom properties to the form, you can define the metadata for them in `Integration#fields`.
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index b9c46a10083..a7a7b5f9eed 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -809,11 +809,14 @@ The following are examples of tests that work for Jest:
```javascript
it('uses some HTML element', () => {
- loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
+ loadHTMLFixture('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
const element = document.getElementById('#my-id');
// ...
+
+ // Jest does not clean up the DOM automatically
+ resetHTMLFixture();
});
```
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
index 17a81419ad0..73674b66f05 100644
--- a/doc/integration/kerberos.md
+++ b/doc/integration/kerberos.md
@@ -299,9 +299,11 @@ Kerberos ticket-based authentication.
## Upgrading from password-based to ticket-based Kerberos sign-ins
In previous versions of GitLab users had to submit their
-Kerberos username and password to GitLab when signing in. We plan to
-remove support for password-based Kerberos sign-ins in a future
-release, so we recommend that you upgrade to ticket-based sign-ins.
+Kerberos username and password to GitLab when signing in.
+
+We [deprecated](../update/deprecations.md#omniauth-kerberos-gem) password-based
+Kerberos sign-ins in GitLab 14.3 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/2908)
+it in GitLab 15.0. You must switch to ticket-based sign in.
Depending on your existing GitLab configuration, the 'Sign in with:
Kerberos SPNEGO' button may already be visible on your GitLab sign-in
diff --git a/doc/operations/index.md b/doc/operations/index.md
index ccdd58d05c8..88687c2faf1 100644
--- a/doc/operations/index.md
+++ b/doc/operations/index.md
@@ -77,13 +77,20 @@ microservices-based distributed systems - and displays results within GitLab.
- [Trace the performance and health](tracing.md) of a deployed application.
-## Aggregate and store logs (DEPRECATED)
+## Aggregate and store logs (DEPRECATED) **(FREE SELF)**
-> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
+> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) behind a [feature flag](../administration/feature_flags.md) named `monitor_logging` in GitLab 15.0. Disabled by default.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
-in GitLab 14.7, and is planned for removal in GitLab 15.0.
+in GitLab 14.7.
+It will be removed completely in GitLab 15.2.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `monitor_logging`.
+On GitLab.com, this feature is not available.
+This feature is not recommended for production use.
Developers need to troubleshoot application changes in development, and incident
responders need aggregated, real-time logs when troubleshooting problems with
diff --git a/doc/update/removals.md b/doc/update/removals.md
index 69330c258ab..c9889bc31de 100644
--- a/doc/update/removals.md
+++ b/doc/update/removals.md
@@ -140,6 +140,16 @@ You should not upgrade to Elasticsearch 8 until you have completed the GitLab 15
View the [version requirements](https://docs.gitlab.com/ee/integration/elasticsearch.html#version-requirements) for details.
+### End of support for Python 3.6 in Dependency Scanning
+
+WARNING:
+This feature was changed or removed in 15.0
+as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
+Before updating GitLab, review the details carefully to determine if you need to make any
+changes to your code, settings, or workflow.
+
+For those using Dependency Scanning for Python projects, we are removing support for the default `gemnasium-python:2` image which uses Python 3.6, as well as the custom `gemnasium-python:2-python-3.9` image which uses Python 3.9. The new default image as of GitLab 15.0 will be for Python 3.9 as it is a [supported version](https://endoflife.date/python) and 3.6 [is no longer supported](https://endoflife.date/python).
+
### External status check API breaking changes
WARNING:
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index d5c8bc61c3a..f50042975b3 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -255,10 +255,23 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
### Supported distributions
-Support depends on the scanner:
-
-- [Grype](https://github.com/anchore/grype#grype)
-- [Trivy](https://aquasecurity.github.io/trivy/latest/docs/vulnerability/detection/os/) (Default).
+Support depends on which scanner is used:
+
+| Distribution | Grype | Trivy |
+| -------------- | ----- | ----- |
+| Alma Linux | | ✅ |
+| Amazon Linux | ✅ | ✅ |
+| BusyBox | ✅ | |
+| CentOS | ✅ | ✅ |
+| CBL-Mariner | | ✅ |
+| Debian | ✅ | ✅ |
+| Distroless | ✅ | ✅ |
+| Oracle Linux | ✅ | ✅ |
+| Photon OS | | ✅ |
+| Red Hat (RHEL) | ✅ | ✅ |
+| Rocky Linux | | ✅ |
+| SUSE | | ✅ |
+| Ubuntu | ✅ | ✅ |
#### FIPS-enabled images
@@ -774,15 +787,38 @@ Here's an example container scanning report:
The [Security Dashboard](../security_dashboard/index.md) shows you an overview of all
the security vulnerabilities in your groups, projects and pipelines.
-## Vulnerabilities database update
+## Vulnerabilities database
All analyzer images are [updated daily](https://gitlab.com/gitlab-org/security-products/analyzers/container-scanning/-/blob/master/README.md#image-updates).
-The images include the latest advisory database available for their respective scanner. Each
-scanner includes data from multiple sources:
-
-- [Grype](https://github.com/anchore/grype#grypes-database).
-- [Trivy](https://aquasecurity.github.io/trivy/latest/docs/vulnerability/detection/data-source/).
+The images use data from upstream advisory databases depending on which scanner is used:
+
+| Data Source | Trivy | Grype |
+| ------------------------------ | ----- | ----- |
+| AlmaLinux Security Advisory | ✅ | ✅ |
+| Amazon Linux Security Center | ✅ | ✅ |
+| Arch Linux Security Tracker | ✅ | |
+| SUSE CVRF | ✅ | ✅ |
+| CWE Advisories | ✅ | |
+| Debian Security Bug Tracker | ✅ | ✅ |
+| GitHub Security Advisory | ✅ | ✅ |
+| Go Vulnerability Database | ✅ | |
+| CBL-Mariner Vulnerability Data | ✅ | |
+| NVD | ✅ | ✅ |
+| OSV | ✅ | |
+| Red Hat OVAL v2 | ✅ | ✅ |
+| Red Hat Security Data API | ✅ | ✅ |
+| Photon Security Advisories | ✅ | |
+| Rocky Linux UpdateInfo | ✅ | |
+| Ubuntu CVE Tracker (only data sources from mid 2021 and later) | ✅ | ✅ |
+
+In addition to the sources provided by these scanners, GitLab maintains the following vulnerability databases:
+
+- The proprietary
+[GitLab Advisory Database](https://gitlab.com/gitlab-org/security-products/gemnasium-db).
+- The open source [GitLab Advisory Database (Open Source Edition)](https://gitlab.com/gitlab-org/advisories-community).
+
+In the GitLab Ultimate tier, the data from the [GitLab Advisory Database](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is merged in to augment the data from the external sources. In the GitLab Premium and Free tiers, the data from the [GitLab Advisory Database (Open Source Edition)](https://gitlab.com/gitlab-org/advisories-community) is merged in to augment the data from the external sources. This augmentation currently only applies to the analyzer images for the Trivy scanner.
Database update information for other analyzers is available in the
[maintenance table](../index.md#vulnerability-scanner-maintenance).
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 1579e400ef9..189cde4e1c1 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -562,7 +562,7 @@ The following variables allow configuration of global dependency scanning settin
| `DS_EXCLUDED_ANALYZERS` | Specify the analyzers (by name) to exclude from Dependency Scanning. For more information, see [Dependency Scanning Analyzers](analyzers.md). |
| `DS_DEFAULT_ANALYZERS` | This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in GitLab 14.8 and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/351963) in 15.0. Use `DS_EXCLUDED_ANALYZERS` instead. |
| `DS_EXCLUDED_PATHS` | Exclude files and directories from the scan based on the paths. A comma-separated list of patterns. Patterns can be globs, or file or folder paths (for example, `doc,spec`). Parent directories also match patterns. Default: `"spec, test, tests, tmp"`. |
-| `DS_IMAGE_SUFFIX` | Suffix added to the image name. If set to `-fips`, `FIPS-enabled` images are used for scan. See [FIPS-enabled images](#fips-enabled-images) for more details. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354796) in GitLab 14.10. |
+| `DS_IMAGE_SUFFIX` | Suffix added to the image name. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354796) in GitLab 14.10.) Automatically set to `"-fips"` when FIPS mode is enabled. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357922) in GitLab 15.0.) |
| `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `SECURE_LOG_LEVEL` | Set the minimum logging level. Messages of this logging level or higher are output. From highest to lowest severity, the logging levels are: `fatal`, `error`, `warn`, `info`, `debug`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10880) in GitLab 13.1. Default: `info`. |
@@ -578,7 +578,7 @@ The following variables are used for configuring specific analyzers (used for a
| `GEMNASIUM_DB_REF_NAME` | `gemnasium` | `master` | Branch name for remote repository database. `GEMNASIUM_DB_REMOTE_URL` is required. |
| `DS_REMEDIATE` | `gemnasium` | `"true"` | Enable automatic remediation of vulnerable dependencies. |
| `GEMNASIUM_LIBRARY_SCAN_ENABLED` | `gemnasium` | `"true"` | Enable detecting vulnerabilities in vendored JavaScript libraries. For now, `gemnasium` leverages [`Retire.js`](https://github.com/RetireJS/retire.js) to do this job. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350512) in GitLab 14.8. |
-| `DS_JAVA_VERSION` | `gemnasium-maven` | `11` | Version of Java. Available versions: `8`, `11`, `13`, `14`, `15`, `16`, `17`. |
+| `DS_JAVA_VERSION` | `gemnasium-maven` | `17` | Version of Java. Available versions: `8`, `11`, `13`, `14`, `15`, `16`, `17`. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repositories). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. |
| `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer passes to `sbt`. |
@@ -644,12 +644,10 @@ Read more on [how to use private Maven repositories](../index.md#using-private-m
GitLab also offers [FIPS-enabled Red Hat UBI](https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image)
versions of the Gemnasium images. You can therefore replace standard images with FIPS-enabled images.
-To use FIPS-enabled images, set the `DS_IMAGE_SUFFIX` to `-fips`.
+Gemnasium scanning jobs automatically use FIPS-enabled image when FIPS mode is enabled in the GitLab instance.
+([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/357922) in GitLab 15.0.)
-```yaml
-variables:
- DS_IMAGE_SUFFIX: "-fips"
-```
+To manually switch to FIPS-enabled images, set the variable `DS_IMAGE_SUFFIX` to `"-fips"`.
## Interacting with the vulnerabilities
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index b259e01ee05..08b17405d55 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -48,7 +48,7 @@ The following vulnerability scanners and their databases are regularly updated:
| Secure scanning tool | Vulnerabilities database updates |
|:----------------------------------------------------------------|:---------------------------------|
-| [Container Scanning](container_scanning/index.md) | A job runs on a daily basis to build new images with the latest vulnerability database updates from the upstream scanner. For more details, see [Vulnerabilities database update](container_scanning/index.md#vulnerabilities-database-update). |
+| [Container Scanning](container_scanning/index.md) | A job runs on a daily basis to build new images with the latest vulnerability database updates from the upstream scanner. For more details, see [Vulnerabilities database update](container_scanning/index.md#vulnerabilities-database). |
| [Dependency Scanning](dependency_scanning/index.md) | Relies on the [GitLab Advisory Database](https://gitlab.com/gitlab-org/security-products/gemnasium-db). It is updated on a daily basis using [data from NVD, the `ruby-advisory-db` and the GitHub Advisory Database as data sources](https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/blob/master/SOURCES.md). See our [current measurement of time from CVE being issued to our product being updated](https://about.gitlab.com/handbook/engineering/development/performance-indicators/#cve-issue-to-update). |
| [Dynamic Application Security Testing (DAST)](dast/index.md) | The scanning engine is updated on a periodic basis. See the [version of the underlying tool `zaproxy`](https://gitlab.com/gitlab-org/security-products/dast/blob/main/Dockerfile#L1). The scanning rules are downloaded at scan runtime. |
| [Static Application Security Testing (SAST)](sast/index.md) | Relies exclusively on [the tools GitLab wraps](sast/index.md#supported-languages-and-frameworks). The underlying analyzers are updated at least once per month if a relevant update is available. The vulnerabilities database is updated by the upstream tools. |
diff --git a/doc/user/clusters/integrations.md b/doc/user/clusters/integrations.md
index 200bd74cac5..3829dee7270 100644
--- a/doc/user/clusters/integrations.md
+++ b/doc/user/clusters/integrations.md
@@ -97,9 +97,21 @@ To enable the Prometheus integration for your cluster:
1. Click **Save changes**.
1. Go to the **Health** tab to see your cluster's metrics.
-## Elastic Stack cluster integration
+## Elastic Stack cluster integration **(FREE SELF)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61077) in GitLab 13.12.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61077) in GitLab 13.12.
+> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.7.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) behind a [feature flag](../../administration/feature_flags.md) named `monitor_logging` in GitLab 15.0. Disabled by default.
+
+WARNING:
+This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485)
+in GitLab 14.7.
+It will be removed completely in GitLab 15.2.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `monitor_logging`.
+On GitLab.com, this feature is not available.
+This feature is not recommended for production use.
You can integrate your cluster with [Elastic
Stack](https://www.elastic.co/elastic-stack/) to index and [query your pod
diff --git a/doc/user/clusters/management_project_template.md b/doc/user/clusters/management_project_template.md
index ba3ff49c92e..8ca1bf5d57f 100644
--- a/doc/user/clusters/management_project_template.md
+++ b/doc/user/clusters/management_project_template.md
@@ -94,7 +94,6 @@ application in the template.
The [built-in supported applications](https://gitlab.com/gitlab-org/project-templates/cluster-management/-/tree/master/applications) are:
- [Cert-manager](../infrastructure/clusters/manage/management_project_applications/certmanager.md)
-- [Elastic Stack](../infrastructure/clusters/manage/management_project_applications/elasticstack.md)
- [GitLab Runner](../infrastructure/clusters/manage/management_project_applications/runner.md)
- [Ingress](../infrastructure/clusters/manage/management_project_applications/ingress.md)
- [Prometheus](../infrastructure/clusters/manage/management_project_applications/prometheus.md)
diff --git a/doc/user/discussions/img/add_internal_note_v15_0.png b/doc/user/discussions/img/add_internal_note_v15_0.png
new file mode 100644
index 00000000000..4662d31ec35
--- /dev/null
+++ b/doc/user/discussions/img/add_internal_note_v15_0.png
Binary files differ
diff --git a/doc/user/discussions/img/confidential_comments_v15_0.png b/doc/user/discussions/img/confidential_comments_v15_0.png
deleted file mode 100644
index 36b7b466b4e..00000000000
--- a/doc/user/discussions/img/confidential_comments_v15_0.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 1cce69415d7..4985f85918b 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -150,10 +150,11 @@ Notes are added to the page details.
If an issue or merge request is locked and closed, you cannot reopen it.
-## Mark a comment as confidential **(FREE SELF)**
+## Add an internal note **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207473) in GitLab 13.9 [with a flag](../../administration/feature_flags.md) named `confidential_notes`. Disabled by default.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/351143) in GitLab 14.10: you can only mark comments in issues and epics as confidential. Previously, it was also possible for comments in merge requests and snippets.
+> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87403) from "confidential comments" to "internal notes" in GitLab 15.0.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
@@ -161,31 +162,33 @@ ask an administrator to [enable the feature flag](../../administration/feature_f
On GitLab.com, this feature is not available.
You should not use this feature for production environments.
-You can make a comment **in an issue or an epic** confidential. It's then visible only to you (the commenting user) and
-the project members who have at least the Reporter role.
+You can add an internal note **to an issue or an epic**. It's then visible only to the following people:
+
+- Project members who have at least the Reporter role
+- Issue or epic author
+- Users assigned to the issue or epic
Keep in mind:
-- You can only mark comments as confidential when you create them.
-- You can't change the confidentiality of existing comments.
-- Replies to comments use same confidentiality as the original comment.
+- Replies to internal notes are also internal.
+- You can not turn an internal note into a regular comment.
Prerequisites:
- You must either:
- Have at least the Reporter role for the project.
- - Be the issue assignee.
- - Be the issue author.
+ - Be the issue or epic assignee.
+ - Be the issue or epic author.
-To mark a comment as confidential:
+To add an internal note:
1. Start adding a new comment.
-1. Below the comment, select the **Make this comment confidential** checkbox.
-1. Select **Comment**.
+1. Below the comment, select the **Make this an internal note** checkbox.
+1. Select **Add internal note**.
-![Confidential comments](img/confidential_comments_v15_0.png)
+![Internal notes](img/add_internal_note_v15_0.png)
-You can also make an [entire issue confidential](../project/issues/confidential_issues.md).
+You can also mark an [issue as confidential](../project/issues/confidential_issues.md).
## Show only comments
diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md
index bacb73f2616..858b13b87b8 100644
--- a/doc/user/group/iterations/index.md
+++ b/doc/user/group/iterations/index.md
@@ -172,15 +172,9 @@ To group issues by label:
## Iteration cadences
-> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5077) in GitLab 14.1.
-> - Deployed behind a [feature flag](../../feature_flags.md), named `iteration_cadences`, disabled by default.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5077) in GitLab 14.1 [with a flag](../../../administration/feature_flags.md), named `iteration_cadences`. Disabled by default.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/354977) in GitLab 15.0: All scheduled iterations must start on the same day of the week as the cadence start day. Start date of cadence cannot be edited after the first iteration starts.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available, ask an
-administrator to [enable the feature flag](../../../administration/feature_flags.md) named
-`iteration_cadences` for a root group.
-On GitLab.com, this feature is not available. This feature is not ready for production use.
+> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/354878) in GitLab 15.0.
Iteration cadences automate iteration scheduling. You can use them to
automate creating iterations every 1, 2, 3, or 4 weeks. You can also
@@ -283,7 +277,7 @@ GitLab will start scheduling new iterations on the same day of the week as the s
starting from the nearest such day from the current date.
During the conversion process GitLab does not delete or modify existing **ongoing** or
-**closed** iterations. If you have iterations with start dates in the ,
+**closed** iterations. If you have iterations with start dates in the future,
they are updated to fit your cadence settings.
#### Converted cadences example
diff --git a/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md b/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md
index e3feedace99..7ab99ab3875 100644
--- a/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md
+++ b/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md
@@ -2,28 +2,11 @@
stage: Monitor
group: Respond
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
+remove_date: '2022-08-22'
+redirect_to: '../../index.md'
---
-# Install Elastic Stack with a cluster management project **(FREE)**
+# Install Elastic Stack with a cluster management project (removed) **(FREE)**
-> [Introduced](https://gitlab.com/gitlab-org/project-templates/cluster-management/-/merge_requests/5) in GitLab 14.0.
-
-Assuming you already have a project created from a
-[management project template](../../../../../user/clusters/management_project_template.md), to install Elastic Stack you should
-uncomment this line from your `helmfile.yaml`:
-
-```yaml
- - path: applications/elastic-stack/helmfile.yaml
-```
-
-Elastic Stack is installed by default into the `gitlab-managed-apps` namespace of your cluster.
-
-You can check the default
-[`values.yaml`](https://gitlab.com/gitlab-org/project-templates/cluster-management/-/blob/master/applications/elastic-stack/values.yaml)
-we set for this chart.
-
-You can customize the installation of Elastic Stack by updating the
-`applications/elastic-stack/values.yaml` file in your cluster
-management project. Refer to the
-[chart](https://gitlab.com/gitlab-org/charts/elastic-stack) for all
-available configuration options.
+This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 14.8
+and [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) in 15.0.
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index b5e2a1bad51..8babb4a9b2c 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -4,14 +4,22 @@ group: Respond
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
-# Kubernetes Logs (DEPRECATED) **(FREE)**
+# Kubernetes Logs (DEPRECATED) **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4752) in GitLab 11.0.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26383) from GitLab Ultimate to GitLab Free 12.9.
> - [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/360182) behind a [feature flag](../../../administration/feature_flags.md) named `monitor_logging` in GitLab 15.0. Disabled by default.
WARNING:
+This feature is in its end-of-life process.
This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
+It will be [removed completely](https://gitlab.com/gitlab-org/gitlab/-/issues/346485) in GitLab 15.2.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `monitor_logging`.
+On GitLab.com, this feature is not available.
+This feature is not recommended for production use.
GitLab makes it easy to view the logs of running pods in
[connected Kubernetes clusters](index.md). By displaying the logs directly in GitLab
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 15130523da6..28967249fa4 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -97,5 +97,5 @@ sees in the project's search results respectively.
- [Merge requests for confidential issues](../merge_requests/confidential.md)
- [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential)
-- [Mark a comment as confidential](../../discussions/index.md#mark-a-comment-as-confidential)
+- [Add an internal note](../../discussions/index.md#add-an-internal-note)
- [Security practices for confidential merge requests](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) at GitLab
diff --git a/doc/user/project/merge_requests/confidential.md b/doc/user/project/merge_requests/confidential.md
index 6900880417f..5b17ec009e4 100644
--- a/doc/user/project/merge_requests/confidential.md
+++ b/doc/user/project/merge_requests/confidential.md
@@ -74,5 +74,5 @@ Open a merge request
- [Confidential issues](../issues/confidential_issues.md)
- [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential)
-- [Mark a comment as confidential](../../discussions/index.md#mark-a-comment-as-confidential)
+- [Add an internal note](../../discussions/index.md#add-an-internal-note)
- [Security practices for confidential merge requests](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) at GitLab
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index c12b3bf5562..2a854bd785e 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -94,7 +94,7 @@ module API
note = create_note(noteable, opts)
- if note.errors.attribute_names == [:commands_only]
+ if note.errors.attribute_names == [:commands_only, :command_names]
status 202
present note, with: Entities::NoteCommands
elsif note.valid?
diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb
index 56590bb9a8f..40e6486dae9 100644
--- a/lib/api/personal_access_tokens.rb
+++ b/lib/api/personal_access_tokens.rb
@@ -39,6 +39,12 @@ module API
def find_token(id)
PersonalAccessToken.find(id) || not_found!
end
+
+ def revoke_token(token)
+ service = ::PersonalAccessTokens::RevokeService.new(current_user, token: token).execute
+
+ service.success? ? no_content! : bad_request!(nil)
+ end
end
resources :personal_access_tokens do
@@ -48,13 +54,14 @@ module API
present paginate(tokens), with: Entities::PersonalAccessToken
end
+ delete 'self' do
+ revoke_token(access_token)
+ end
+
delete ':id' do
- service = ::PersonalAccessTokens::RevokeService.new(
- current_user,
- token: find_token(params[:id])
- ).execute
+ token = find_token(params[:id])
- service.success? ? no_content! : bad_request!(nil)
+ revoke_token(token)
end
end
end
diff --git a/lib/event_filter.rb b/lib/event_filter.rb
index 8833207dd1d..8c3377fdb80 100644
--- a/lib/event_filter.rb
+++ b/lib/event_filter.rb
@@ -15,6 +15,8 @@ class EventFilter
WIKI = 'wiki'
DESIGNS = 'designs'
+ PROJECT_ONLY_EVENT_TYPES = [PUSH, MERGED, TEAM, ISSUE, DESIGNS].freeze
+
def initialize(filter)
# Split using comma to maintain backward compatibility Ex/ "filter1,filter2"
filter = filter.to_s.split(',')[0].to_s
@@ -49,13 +51,15 @@ class EventFilter
# rubocop: disable Metrics/CyclomaticComplexity
# This method build specialized in-operator optimized queries based on different
# filter parameters. All queries will benefit from the index covering the following columns:
- # author_id target_type action id
+ # * author_id target_type action id
+ # * project_id target_type action id
+ # * group_id target_type action id
#
# More context: https://docs.gitlab.com/ee/development/database/efficient_in_operator_queries.html#the-inoperatoroptimization-module
- def in_operator_query_builder_params(user_ids)
+ def in_operator_query_builder_params(array_data)
case filter
when ALL
- in_operator_params(array_scope_ids: user_ids)
+ in_operator_params(array_data: array_data)
when PUSH
# Here we need to add an order hint column to force the correct index usage.
# Without the order hint, the following conditions will use the `index_events_on_author_id_and_id`
@@ -66,25 +70,25 @@ class EventFilter
# to use the correct index:
# > target_type IS NULL AND action = 5 AND author_id = X ORDER BY target_type DESC, id DESC
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.where(target_type: nil).pushed_action,
order_hint_column: :target_type
)
when MERGED
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.where(target_type: MergeRequest.to_s).merged_action
)
when COMMENTS
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.commented_action,
in_column: :target_type,
in_values: [Note, *Note.descendants].map(&:name) # To make the query efficient we need to list all Note classes
)
when TEAM
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.where(target_type: nil),
order_hint_column: :target_type,
in_column: :action,
@@ -92,34 +96,34 @@ class EventFilter
)
when ISSUE
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.where(target_type: Issue.name),
in_column: :action,
in_values: Event.actions.values_at(*Event::ISSUE_ACTIONS)
)
when WIKI
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.for_wiki_page,
in_column: :action,
in_values: Event.actions.values_at(*Event::WIKI_ACTIONS)
)
when DESIGNS
in_operator_params(
- array_scope_ids: user_ids,
+ array_data: array_data,
scope: Event.for_design,
in_column: :action,
in_values: Event.actions.values_at(*Event::DESIGN_ACTIONS)
)
else
- in_operator_params(array_scope_ids: user_ids)
+ in_operator_params(array_data: array_data)
end
end
# rubocop: enable Metrics/CyclomaticComplexity
private
- def in_operator_params(array_scope_ids:, scope: nil, in_column: nil, in_values: nil, order_hint_column: nil)
+ def in_operator_params(array_data:, scope: nil, in_column: nil, in_values: nil, order_hint_column: nil)
base_scope = Event.all
base_scope = base_scope.merge(scope) if scope
@@ -146,8 +150,8 @@ class EventFilter
base_scope = base_scope.reorder(order)
array_params = in_operator_array_params(
- array_scope_ids: array_scope_ids,
scope: base_scope,
+ array_data: array_data,
in_column: in_column,
in_values: in_values
)
@@ -161,22 +165,30 @@ class EventFilter
# This method builds the array_ parameters
# without in_column parameter: uses one IN filter: author_id
# with in_column: two IN filters: author_id, (target_type OR action)
- def in_operator_array_params(scope:, array_scope_ids:, in_column: nil, in_values: nil)
+ # @param array_data [Hash] Must contain the scope_ids, scope_model, mapping_column keys
+ def in_operator_array_params(scope:, array_data:, in_column: nil, in_values: nil)
+ array_scope_ids = array_data[:scope_ids]
+ array_scope_model = array_data[:scope_model]
+ array_mapping_column = array_data[:mapping_column]
+
+ # Adding non-existent record to generate valid SQL if array_scope_ids is empty
+ array_scope_ids << 0 if array_scope_ids.empty?
+
if in_column
- # Builds Carthesian product of the in_values and the array_scope_ids (in this case: user_ids).
+ # Builds Cartesian product of the in_values and the array_scope_ids (in this case: user_ids).
# The process is described here: https://docs.gitlab.com/ee/development/database/efficient_in_operator_queries.html#multiple-in-queries
# VALUES ((array_scope_ids[0], in_values[0]), (array_scope_ids[1], in_values[0]) ...)
cartesian = array_scope_ids.product(in_values)
- user_with_column_list = Arel::Nodes::ValuesList.new(cartesian)
+ column_list = Arel::Nodes::ValuesList.new(cartesian)
as = "array_ids(id, #{Event.connection.quote_column_name(in_column)})"
- from = Arel::Nodes::Grouping.new(user_with_column_list).as(as)
+ from = Arel::Nodes::Grouping.new(column_list).as(as)
{
- array_scope: User.select(:id, in_column).from(from),
- array_mapping_scope: -> (author_id_expression, in_column_expression) do
+ array_scope: array_scope_model.select(:id, in_column).from(from),
+ array_mapping_scope: -> (primary_id_expression, in_column_expression) do
Event
.merge(scope)
- .where(Event.arel_table[:author_id].eq(author_id_expression))
+ .where(Event.arel_table[array_mapping_column].eq(primary_id_expression))
.where(Event.arel_table[in_column].eq(in_column_expression))
end
}
@@ -186,11 +198,11 @@ class EventFilter
array_ids_list = Arel::Nodes::ValuesList.new(array_scope_ids.map { |id| [id] })
from = Arel::Nodes::Grouping.new(array_ids_list).as('array_ids(id)')
{
- array_scope: User.select(:id).from(from),
- array_mapping_scope: -> (author_id_expression) do
+ array_scope: array_scope_model.select(:id).from(from),
+ array_mapping_scope: -> (primary_id_expression) do
Event
.merge(scope)
- .where(Event.arel_table[:author_id].eq(author_id_expression))
+ .where(Event.arel_table[array_mapping_column].eq(primary_id_expression))
end
}
end
diff --git a/lib/gitlab/application_rate_limiter.rb b/lib/gitlab/application_rate_limiter.rb
index 09775297def..41a6cbc2543 100644
--- a/lib/gitlab/application_rate_limiter.rb
+++ b/lib/gitlab/application_rate_limiter.rb
@@ -59,6 +59,8 @@ module Gitlab
def throttled?(key, scope:, threshold: nil, users_allowlist: nil, peek: false)
raise InvalidKeyError unless rate_limits[key]
+ ::Gitlab::Instrumentation::RateLimitingGates.track(key)
+
return false if scoped_user_in_allowlist?(scope, users_allowlist)
threshold_value = threshold || threshold(key)
diff --git a/lib/gitlab/background_migration/expire_o_auth_tokens.rb b/lib/gitlab/background_migration/expire_o_auth_tokens.rb
index 4ba7d919cc7..595e4ac9dc8 100644
--- a/lib/gitlab/background_migration/expire_o_auth_tokens.rb
+++ b/lib/gitlab/background_migration/expire_o_auth_tokens.rb
@@ -4,18 +4,18 @@ module Gitlab
module BackgroundMigration
# Add expiry to all OAuth access tokens
class ExpireOAuthTokens < ::Gitlab::BackgroundMigration::BatchedMigrationJob
- def perform(batch_size)
+ def perform
each_sub_batch(
operation_name: :update_oauth_tokens,
batching_scope: ->(relation) { relation.where(expires_in: nil) }
) do |sub_batch|
- update_oauth_tokens(sub_batch, batch_size)
+ update_oauth_tokens(sub_batch)
end
end
private
- def update_oauth_tokens(relation, batch_size)
+ def update_oauth_tokens(relation)
relation.update_all(expires_in: 7_200)
end
end
diff --git a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
index 6dd55d00ae9..7c6122aa419 100644
--- a/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Dependency-Scanning.gitlab-ci.yml
@@ -51,6 +51,18 @@ dependency_scanning:
paths:
- "**/cyclonedx-*.json"
+.gemnasium-shared-rule:
+ exists:
+ - '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
+ - '{composer.lock,*/composer.lock,*/*/composer.lock}'
+ - '{gems.locked,*/gems.locked,*/*/gems.locked}'
+ - '{go.sum,*/go.sum,*/*/go.sum}'
+ - '{npm-shrinkwrap.json,*/npm-shrinkwrap.json,*/*/npm-shrinkwrap.json}'
+ - '{package-lock.json,*/package-lock.json,*/*/package-lock.json}'
+ - '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
+ - '{packages.lock.json,*/packages.lock.json,*/*/packages.lock.json}'
+ - '{conan.lock,*/conan.lock,*/*/conan.lock}'
+
gemnasium-dependency_scanning:
extends:
- .ds-analyzer
@@ -64,17 +76,21 @@ gemnasium-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium([^-]|$)/
when: never
- if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
+ $CI_GITLAB_FIPS_MODE == "true"
+ exists: !reference [.gemnasium-shared-rule, exists]
+ variables:
+ DS_IMAGE_SUFFIX: "-fips"
+ - if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/
- exists:
- - '{Gemfile.lock,*/Gemfile.lock,*/*/Gemfile.lock}'
- - '{composer.lock,*/composer.lock,*/*/composer.lock}'
- - '{gems.locked,*/gems.locked,*/*/gems.locked}'
- - '{go.sum,*/go.sum,*/*/go.sum}'
- - '{npm-shrinkwrap.json,*/npm-shrinkwrap.json,*/*/npm-shrinkwrap.json}'
- - '{package-lock.json,*/package-lock.json,*/*/package-lock.json}'
- - '{yarn.lock,*/yarn.lock,*/*/yarn.lock}'
- - '{packages.lock.json,*/packages.lock.json,*/*/packages.lock.json}'
- - '{conan.lock,*/conan.lock,*/*/conan.lock}'
+ exists: !reference [.gemnasium-shared-rule, exists]
+
+.gemnasium-maven-shared-rule:
+ exists:
+ - '{build.gradle,*/build.gradle,*/*/build.gradle}'
+ - '{build.gradle.kts,*/build.gradle.kts,*/*/build.gradle.kts}'
+ - '{build.sbt,*/build.sbt,*/*/build.sbt}'
+ - '{pom.xml,*/pom.xml,*/*/pom.xml}'
gemnasium-maven-dependency_scanning:
extends:
@@ -88,12 +104,22 @@ gemnasium-maven-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium-maven/
when: never
- if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
+ $CI_GITLAB_FIPS_MODE == "true"
+ exists: !reference [.gemnasium-maven-shared-rule, exists]
+ variables:
+ DS_IMAGE_SUFFIX: "-fips"
+ - if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/
- exists:
- - '{build.gradle,*/build.gradle,*/*/build.gradle}'
- - '{build.gradle.kts,*/build.gradle.kts,*/*/build.gradle.kts}'
- - '{build.sbt,*/build.sbt,*/*/build.sbt}'
- - '{pom.xml,*/pom.xml,*/*/pom.xml}'
+ exists: !reference [.gemnasium-maven-shared-rule, exists]
+
+.gemnasium-python-shared-rule:
+ exists:
+ - '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
+ - '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
+ - '{Pipfile,*/Pipfile,*/*/Pipfile}'
+ - '{requires.txt,*/requires.txt,*/*/requires.txt}'
+ - '{setup.py,*/setup.py,*/*/setup.py}'
gemnasium-python-dependency_scanning:
extends:
@@ -107,15 +133,22 @@ gemnasium-python-dependency_scanning:
- if: $DS_EXCLUDED_ANALYZERS =~ /gemnasium-python/
when: never
- if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
+ $CI_GITLAB_FIPS_MODE == "true"
+ exists: !reference [.gemnasium-python-shared-rule, exists]
+ variables:
+ DS_IMAGE_SUFFIX: "-fips"
+ - if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/
- exists:
- - '{requirements.txt,*/requirements.txt,*/*/requirements.txt}'
- - '{requirements.pip,*/requirements.pip,*/*/requirements.pip}'
- - '{Pipfile,*/Pipfile,*/*/Pipfile}'
- - '{requires.txt,*/requires.txt,*/*/requires.txt}'
- - '{setup.py,*/setup.py,*/*/setup.py}'
- # Support passing of $PIP_REQUIREMENTS_FILE
- # See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#configuring-specific-analyzers-used-by-dependency-scanning
+ exists: !reference [.gemnasium-python-shared-rule, exists]
+ # Support passing of $PIP_REQUIREMENTS_FILE
+ # See https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#configuring-specific-analyzers-used-by-dependency-scanning
+ - if: $CI_COMMIT_BRANCH &&
+ $GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
+ $PIP_REQUIREMENTS_FILE &&
+ $CI_GITLAB_FIPS_MODE == "true"
+ variables:
+ DS_IMAGE_SUFFIX: "-fips"
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bdependency_scanning\b/ &&
$PIP_REQUIREMENTS_FILE
diff --git a/lib/gitlab/instrumentation/rate_limiting_gates.rb b/lib/gitlab/instrumentation/rate_limiting_gates.rb
new file mode 100644
index 00000000000..960b6995030
--- /dev/null
+++ b/lib/gitlab/instrumentation/rate_limiting_gates.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Instrumentation
+ class RateLimitingGates
+ GATES = :rate_limiting_gates
+
+ class << self
+ def track(key)
+ if ::Gitlab::SafeRequestStore.active?
+ gates_set << key
+ end
+ end
+
+ def gates
+ gates_set.to_a
+ end
+
+ def payload
+ {
+ GATES => gates
+ }
+ end
+
+ private
+
+ def gates_set
+ ::Gitlab::SafeRequestStore[GATES] ||= Set.new
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb
index 155e365d04c..379c27caeb7 100644
--- a/lib/gitlab/instrumentation_helper.rb
+++ b/lib/gitlab/instrumentation_helper.rb
@@ -32,6 +32,7 @@ module Gitlab
instrument_load_balancing(payload)
instrument_pid(payload)
instrument_uploads(payload)
+ instrument_rate_limiting_gates(payload)
end
def instrument_gitaly(payload)
@@ -121,6 +122,10 @@ module Gitlab
payload.merge! ::Gitlab::Instrumentation::Uploads.payload
end
+ def instrument_rate_limiting_gates(payload)
+ payload.merge!(::Gitlab::Instrumentation::RateLimitingGates.payload)
+ end
+
# Returns the queuing duration for a Sidekiq job in seconds, as a float, if the
# `enqueued_at` field or `created_at` field is available.
#
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index 51277497c99..b78cd2a6b95 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -136,8 +136,6 @@ module Gitlab
def setup_provider(provider)
case provider
- when :kerberos
- require 'omniauth-kerberos'
when *omniauth_customized_providers
require_dependency "omni_auth/strategies/#{provider}"
end
diff --git a/lib/sidebars/projects/menus/monitor_menu.rb b/lib/sidebars/projects/menus/monitor_menu.rb
index 59554726263..a42e0db55ee 100644
--- a/lib/sidebars/projects/menus/monitor_menu.rb
+++ b/lib/sidebars/projects/menus/monitor_menu.rb
@@ -58,7 +58,8 @@ module Sidebars
end
def logs_menu_item
- if !can?(context.current_user, :read_environment, context.project) ||
+ if !Feature.enabled?(:monitor_logging, context.project) ||
+ !can?(context.current_user, :read_environment, context.project) ||
!can?(context.current_user, :read_pod_logs, context.project)
return ::Sidebars::NilMenuItem.new(item_id: :logs)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 1e4d43ad7e6..c4babc1ae93 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -781,6 +781,9 @@ msgstr ""
msgid "%{message} showing first %{warnings_displayed}"
msgstr ""
+msgid "%{message}. Your attention request was removed."
+msgstr ""
+
msgid "%{milestone} (expired)"
msgstr ""
@@ -4729,6 +4732,9 @@ msgstr ""
msgid "Approved-By"
msgstr ""
+msgid "Approved. Your attention request was removed."
+msgstr ""
+
msgid "Approver"
msgstr ""
@@ -5058,6 +5064,9 @@ msgstr ""
msgid "Assigned to you"
msgstr ""
+msgid "Assigned user(s). Your attention request was removed."
+msgstr ""
+
msgid "Assignee"
msgid_plural "%d Assignees"
msgstr[0] ""
@@ -5638,6 +5647,78 @@ msgstr ""
msgid "Batch size"
msgstr ""
+msgid "Batched Job|Background Migrations"
+msgstr ""
+
+msgid "Batched Job|Batched Job (Id: %{id})"
+msgstr ""
+
+msgid "BatchedJob|Attempts"
+msgstr ""
+
+msgid "BatchedJob|Batch size"
+msgstr ""
+
+msgid "BatchedJob|Batched Jobs"
+msgstr ""
+
+msgid "BatchedJob|Created At"
+msgstr ""
+
+msgid "BatchedJob|Created at"
+msgstr ""
+
+msgid "BatchedJob|Exception Class"
+msgstr ""
+
+msgid "BatchedJob|Exception Message"
+msgstr ""
+
+msgid "BatchedJob|Exception class"
+msgstr ""
+
+msgid "BatchedJob|Exception message"
+msgstr ""
+
+msgid "BatchedJob|Finished at"
+msgstr ""
+
+msgid "BatchedJob|Max Value"
+msgstr ""
+
+msgid "BatchedJob|Max value"
+msgstr ""
+
+msgid "BatchedJob|Min Value"
+msgstr ""
+
+msgid "BatchedJob|Min value"
+msgstr ""
+
+msgid "BatchedJob|Next Status"
+msgstr ""
+
+msgid "BatchedJob|Next status"
+msgstr ""
+
+msgid "BatchedJob|Pause ms"
+msgstr ""
+
+msgid "BatchedJob|Pause time (ms)"
+msgstr ""
+
+msgid "BatchedJob|Previous Status"
+msgstr ""
+
+msgid "BatchedJob|Previous status"
+msgstr ""
+
+msgid "BatchedJob|Started at"
+msgstr ""
+
+msgid "BatchedJob|Transition logs:"
+msgstr ""
+
msgid "Be careful. Changing the project's namespace can have unintended side effects."
msgstr ""
@@ -21891,6 +21972,9 @@ msgstr ""
msgid "Jump to next unresolved thread"
msgstr ""
+msgid "Jump to previous unresolved thread"
+msgstr ""
+
msgid "Jun"
msgstr ""
@@ -31947,12 +32031,18 @@ msgstr ""
msgid "Requested attention from @%{username}"
msgstr ""
+msgid "Requested attention from @%{username}. Your own attention request was removed."
+msgstr ""
+
msgid "Requested design version does not exist."
msgstr ""
msgid "Requested review"
msgstr ""
+msgid "Requested review. Your attention request was removed."
+msgstr ""
+
msgid "Requested states are invalid"
msgstr ""
@@ -40456,6 +40546,9 @@ msgstr ""
msgid "UsageQuota|Buy additional minutes"
msgstr ""
+msgid "UsageQuota|Buy storage"
+msgstr ""
+
msgid "UsageQuota|CI minutes usage by month"
msgstr ""
@@ -40501,9 +40594,15 @@ msgstr ""
msgid "UsageQuota|Learn more about usage quotas"
msgstr ""
+msgid "UsageQuota|Learn more about usage quotas."
+msgstr ""
+
msgid "UsageQuota|Local proxy used for frequently-accessed upstream Docker images. %{linkStart}More information%{linkEnd}"
msgstr ""
+msgid "UsageQuota|Namespace storage used"
+msgstr ""
+
msgid "UsageQuota|No CI minutes usage data available."
msgstr ""
@@ -40522,9 +40621,15 @@ msgstr ""
msgid "UsageQuota|Purchase more storage"
msgstr ""
+msgid "UsageQuota|Purchased storage"
+msgstr ""
+
msgid "UsageQuota|Purchased storage available"
msgstr ""
+msgid "UsageQuota|Purchased storage used"
+msgstr ""
+
msgid "UsageQuota|Repository"
msgstr ""
diff --git a/package.json b/package.json
index c93020594f3..eed49746743 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.14.0",
- "@gitlab/ui": "40.2.0",
+ "@gitlab/ui": "40.2.1",
"@gitlab/visual-review-tools": "1.7.1",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",
diff --git a/qa/qa/page/project/settings/services/prometheus.rb b/qa/qa/page/project/settings/services/prometheus.rb
index 8ae4ded535e..2e3c385a27d 100644
--- a/qa/qa/page/project/settings/services/prometheus.rb
+++ b/qa/qa/page/project/settings/services/prometheus.rb
@@ -8,7 +8,7 @@ module QA
class Prometheus < Page::Base
include Page::Component::CustomMetric
- view 'app/views/projects/services/prometheus/_custom_metrics.html.haml' do
+ view 'app/views/shared/integrations/prometheus/_custom_metrics.html.haml' do
element :custom_metrics_container
element :new_metric_button
end
diff --git a/spec/controllers/projects/logs_controller_spec.rb b/spec/controllers/projects/logs_controller_spec.rb
index 48d82472eb0..1c81ae93b42 100644
--- a/spec/controllers/projects/logs_controller_spec.rb
+++ b/spec/controllers/projects/logs_controller_spec.rb
@@ -47,6 +47,20 @@ RSpec.describe Projects::LogsController do
expect(response).to be_ok
expect(response).to render_template 'index'
end
+
+ context 'with feature flag disabled' do
+ before do
+ stub_feature_flags(monitor_logging: false)
+ end
+
+ it 'returns 404 with reporter access' do
+ project.add_developer(user)
+
+ get :index, params: environment_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
shared_examples 'pod logs service' do |endpoint, service|
diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb
index f6048ad2515..8edddcf9a9b 100644
--- a/spec/features/admin/admin_sees_background_migrations_spec.rb
+++ b/spec/features/admin/admin_sees_background_migrations_spec.rb
@@ -54,6 +54,19 @@ RSpec.describe "Admin > Admin sees background migrations" do
end
end
+ it 'can click on a specific job' do
+ job = create(:batched_background_migration_job, :failed, batched_migration: failed_migration)
+
+ visit admin_background_migration_path(failed_migration)
+
+ within '#content-body' do
+ tab = find_link job.id
+ tab.click
+
+ expect(page).to have_current_path admin_background_migration_batched_job_path(id: job.id, background_migration_id: failed_migration.id)
+ end
+ end
+
context 'when there are no failed jobs' do
it 'dos not display failed jobs' do
visit admin_background_migration_path(active_migration)
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index 4d59e1ded3d..3c774f8b269 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -78,14 +78,14 @@ RSpec.describe 'Dashboard Issues filtering', :js do
end
it 'remembers last sorting value' do
- sort_by('Created date')
+ pajamas_sort_by(s_('SortOptions|Created date'))
visit_issues(assignee_username: user.username)
expect(page).to have_button('Created date')
end
it 'keeps sorting issues after visiting Projects Issues page' do
- sort_by('Created date')
+ pajamas_sort_by(s_('SortOptions|Created date'))
visit project_issues_path(project)
expect(page).to have_button('Created date')
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 38bef5c6143..fd580b679ad 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -115,6 +115,9 @@ RSpec.describe 'Dashboard Merge Requests' do
within("span[aria-label='#{n_("%d merge request", "%d merge requests", 0) % 0}']") do
expect(page).to have_content('0')
end
+
+ find('.dashboard-shortcuts-merge_requests').click
+
expect(find('.js-assigned-mr-count')).to have_content('2')
expect(find('.js-reviewer-mr-count')).to have_content('1')
end
@@ -165,16 +168,16 @@ RSpec.describe 'Dashboard Merge Requests' do
expect(page).to have_content('Please select at least one filter to see results')
end
- it 'shows sorted merge requests' do
- sort_by('Created date')
+ it 'shows sorted merge requests', :js do
+ pajamas_sort_by(s_('SortOptions|Created date'))
visit merge_requests_dashboard_path(assignee_username: current_user.username)
expect(find('.issues-filters')).to have_content('Created date')
end
- it 'keeps sorting merge requests after visiting Projects MR page' do
- sort_by('Created date')
+ it 'keeps sorting merge requests after visiting Projects MR page', :js do
+ pajamas_sort_by(s_('SortOptions|Created date'))
visit project_merge_requests_path(project)
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index bc40fb713ac..53723b39d5b 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -88,14 +88,14 @@ RSpec.describe 'Sort Issuable List' do
end
end
- context 'custom sorting' do
+ context 'custom sorting', :js do
let(:issuable_type) { :merge_request }
it 'supports sorting in asc and desc order' do
visit_merge_requests_with_state(project, 'open')
click_button('Created date')
- click_link('Updated date')
+ find('.dropdown-item', text: 'Updated date').click
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index b87ac743d02..1e8b9b6b60b 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe 'Resolving all open threads in a merge request from an issue', :j
end
it 'shows a button to resolve all threads by creating a new issue' do
- within('.line-resolve-all-container') do
+ within('.discussions-counter') do
expect(page).to have_selector resolve_all_discussions_link_selector( title: "Create issue to resolve all threads" )
end
end
diff --git a/spec/features/merge_request/batch_comments_spec.rb b/spec/features/merge_request/batch_comments_spec.rb
index 2d8d4064efd..9b54d95be6b 100644
--- a/spec/features/merge_request/batch_comments_spec.rb
+++ b/spec/features/merge_request/batch_comments_spec.rb
@@ -172,9 +172,8 @@ RSpec.describe 'Merge request > Batch comments', :js do
write_reply_to_discussion(button_text: 'Add comment now', resolve: true)
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -189,9 +188,8 @@ RSpec.describe 'Merge request > Batch comments', :js do
wait_for_requests
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
end
@@ -212,9 +210,8 @@ RSpec.describe 'Merge request > Batch comments', :js do
write_reply_to_discussion(button_text: 'Add comment now', unresolve: true)
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
- expect(page).not_to have_selector('.line-resolve-btn.is-active')
end
end
@@ -231,9 +228,8 @@ RSpec.describe 'Merge request > Batch comments', :js do
wait_for_requests
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
- expect(page).not_to have_selector('.line-resolve-btn.is-active')
end
end
end
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 231722c166d..e09ec11f095 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
context 'single thread' do
it 'shows text with how many threads' do
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -55,9 +55,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -72,9 +71,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_selector('.line-resolve-btn.is-active')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -84,7 +82,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
click_button 'Unresolve thread'
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -155,7 +153,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
wait_for_requests
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
end
end
@@ -167,9 +165,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
wait_for_requests
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
- expect(page).not_to have_selector('.line-resolve-btn.is-active')
end
end
@@ -184,7 +181,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
wait_for_requests
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -196,9 +193,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -213,14 +209,13 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
click_button 'Add comment now'
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
it 'allows user to quickly scroll to next unresolved thread' do
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
page.find('.discussion-next-btn').click
end
@@ -269,7 +264,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(first('.line-resolve-btn')['aria-label']).to eq("Resolved by #{user.name}")
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
end
end
@@ -286,7 +281,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(button['aria-label']).to eq("Resolved by #{user.name}")
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
end
end
@@ -299,7 +294,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
end
it 'shows text with how many threads' do
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('2 unresolved threads')
end
end
@@ -307,7 +302,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to mark a single note as resolved' do
click_button('Resolve thread', match: :first)
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -317,9 +312,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
btn.click
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -330,9 +324,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
end
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -341,7 +334,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
page.find('.discussion-next-btn').click
end
@@ -370,7 +363,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).not_to have_selector('.btn', text: 'Resolve thread')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
page.find('.discussion-next-btn').click
end
@@ -386,7 +379,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
context 'changes tab' do
it 'shows text with how many threads' do
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -402,9 +395,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -417,9 +409,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_selector('.line-resolve-btn.is-active')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -429,7 +420,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
click_button 'Unresolve thread'
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -445,9 +436,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
click_button 'Add comment now'
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -462,7 +452,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
click_button 'Add comment now'
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -485,7 +475,7 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).not_to have_selector('.line-resolve-btn')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
@@ -515,9 +505,8 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_selector('.btn', text: 'Unresolve thread')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('All threads resolved')
- expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
end
@@ -534,32 +523,11 @@ RSpec.describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).not_to have_selector('.line-resolve-btn')
end
- page.within '.line-resolve-all-container' do
+ page.within '.discussions-counter' do
expect(page).to have_content('1 unresolved thread')
end
end
end
-
- context 'resolved comment' do
- before do
- note.resolve!(user)
- visit_merge_request
- end
-
- it 'shows resolved icon' do
- expect(page).to have_content 'All threads resolved'
-
- click_button _('Show thread')
- expect(page).to have_selector('.line-resolve-btn.is-active')
- end
-
- it 'does not allow user to click resolve button' do
- expect(page).to have_selector('.line-resolve-btn.is-active')
- click_button _('Show thread')
-
- expect(page).to have_selector('.line-resolve-btn.is-active')
- end
- end
end
def visit_merge_request(mr = nil)
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 8c1d9dd38b0..2743f7e8ed4 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe 'Merge requests > User lists merge requests' do
expect(count_merge_requests).to eq(4)
end
- it 'sorts by milestone' do
+ it 'sorts by milestone due date' do
visit_merge_requests(project, sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
@@ -130,12 +130,12 @@ RSpec.describe 'Merge requests > User lists merge requests' do
expect(count_merge_requests).to eq(4)
end
- it 'filters on one label and sorts by due date' do
+ it 'filters on one label and sorts by milestone due date' do
label = create(:label, project: project)
create(:label_link, label: label, target: @fix)
visit_merge_requests(project, label_name: [label.name],
- sort: sort_value_due_date)
+ sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
@@ -150,19 +150,19 @@ RSpec.describe 'Merge requests > User lists merge requests' do
create(:label_link, label: label2, target: @fix)
end
- it 'sorts by due date' do
+ it 'sorts by milestone due date' do
visit_merge_requests(project, label_name: [label.name, label2.name],
- sort: sort_value_due_date)
+ sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
end
context 'filter on assignee and' do
- it 'sorts by due soon' do
+ it 'sorts by milestone due date' do
visit_merge_requests(project, label_name: [label.name, label2.name],
assignee_id: user.id,
- sort: sort_value_due_date)
+ sort: sort_value_milestone)
expect(first_merge_request).to include('fix')
expect(count_merge_requests).to eq(1)
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index a15b6072e70..fa866beb773 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -5,12 +5,14 @@ require 'spec_helper'
RSpec.describe 'Merge requests > User mass updates', :js do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
+ let(:user2) { create(:user) }
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
before do
stub_feature_flags(mr_attention_requests: false)
project.add_maintainer(user)
+ project.add_maintainer(user2)
sign_in(user)
end
@@ -68,9 +70,9 @@ RSpec.describe 'Merge requests > User mass updates', :js do
end
it 'updates merge request with assignee' do
- change_assignee(user.name)
+ change_assignee(user2.name)
- expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user.name}"
+ expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user2.name}"
end
end
end
diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index fba7facaa1f..459145d3ef0 100644
--- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
@@ -2,8 +2,9 @@
require 'spec_helper'
-RSpec.describe 'User sorts merge requests' do
+RSpec.describe 'User sorts merge requests', :js do
include CookieHelper
+ include Spec::Support::Helpers::Features::SortingHelpers
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let!(:merge_request2) do
@@ -24,23 +25,19 @@ RSpec.describe 'User sorts merge requests' do
end
it 'keeps the sort option' do
- find('.filter-dropdown-container .dropdown').click
-
- page.within('ul.dropdown-menu.dropdown-menu-right li') do
- click_link('Milestone')
- end
+ pajamas_sort_by(s_('SortOptions|Milestone'))
visit(merge_requests_dashboard_path(assignee_username: user.username))
- expect(find('.issues-filters a.is-active')).to have_content('Milestone')
+ expect(find('.filter-dropdown-container button.dropdown-toggle')).to have_content('Milestone')
visit(project_merge_requests_path(project))
- expect(find('.issues-filters a.is-active')).to have_content('Milestone')
+ expect(find('.filter-dropdown-container button.dropdown-toggle')).to have_content('Milestone')
visit(merge_requests_group_path(group))
- expect(find('.issues-filters a.is-active')).to have_content('Milestone')
+ expect(find('.filter-dropdown-container button.dropdown-toggle')).to have_content('Milestone')
end
it 'fallbacks to issuable_sort cookie key when remembering the sorting option' do
@@ -48,17 +45,13 @@ RSpec.describe 'User sorts merge requests' do
visit(merge_requests_dashboard_path(assignee_username: user.username))
- expect(find('.issues-filters a.is-active')).to have_content('Milestone')
+ expect(find('.filter-dropdown-container button.dropdown-toggle')).to have_content('Milestone')
end
it 'separates remember sorting with issues', :js do
create(:issue, project: project)
- find('.filter-dropdown-container .dropdown').click
-
- page.within('ul.dropdown-menu.dropdown-menu-right li') do
- click_link('Milestone')
- end
+ pajamas_sort_by(s_('SortOptions|Milestone'))
visit(project_issues_path(project))
@@ -75,11 +68,7 @@ RSpec.describe 'User sorts merge requests' do
end
it 'sorts by popularity' do
- find('.filter-dropdown-container .dropdown').click
-
- page.within('ul.dropdown-menu.dropdown-menu-right li') do
- click_link('Popularity')
- end
+ pajamas_sort_by(s_('SortOptions|Popularity'))
page.within('.mr-list') do
page.within('li.merge-request:nth-child(1)') do
diff --git a/spec/features/projects/pipelines/legacy_pipeline_spec.rb b/spec/features/projects/pipelines/legacy_pipeline_spec.rb
new file mode 100644
index 00000000000..a29cef393a8
--- /dev/null
+++ b/spec/features/projects/pipelines/legacy_pipeline_spec.rb
@@ -0,0 +1,1073 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Pipeline', :js do
+ include RoutesHelpers
+ include ProjectForksHelper
+ include ::ExclusiveLeaseHelpers
+
+ let_it_be(:project) { create(:project) }
+
+ let(:user) { create(:user) }
+ let(:role) { :developer }
+
+ before do
+ sign_in(user)
+ project.add_role(user, role)
+ stub_feature_flags(pipeline_tabs_vue: false)
+ end
+
+ shared_context 'pipeline builds' do
+ let!(:build_passed) do
+ create(:ci_build, :success,
+ pipeline: pipeline, stage: 'build', stage_idx: 0, name: 'build')
+ end
+
+ let!(:build_failed) do
+ create(:ci_build, :failed,
+ pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'test')
+ end
+
+ let!(:build_preparing) do
+ create(:ci_build, :preparing,
+ pipeline: pipeline, stage: 'deploy', stage_idx: 2, name: 'prepare')
+ end
+
+ let!(:build_running) do
+ create(:ci_build, :running,
+ pipeline: pipeline, stage: 'deploy', stage_idx: 3, name: 'deploy')
+ end
+
+ let!(:build_manual) do
+ create(:ci_build, :manual,
+ pipeline: pipeline, stage: 'deploy', stage_idx: 3, name: 'manual-build')
+ end
+
+ let!(:build_scheduled) do
+ create(:ci_build, :scheduled,
+ pipeline: pipeline, stage: 'deploy', stage_idx: 3, name: 'delayed-job')
+ end
+
+ let!(:build_external) do
+ create(:generic_commit_status, status: 'success',
+ pipeline: pipeline,
+ name: 'jenkins',
+ stage: 'external',
+ ref: 'master',
+ target_url: 'http://gitlab.com/status')
+ end
+ end
+
+ describe 'GET /:project/-/pipelines/:id' do
+ include_context 'pipeline builds'
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
+
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
+
+ subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) }
+
+ it 'shows the pipeline graph' do
+ visit_pipeline
+
+ expect(page).to have_selector('.js-pipeline-graph')
+ expect(page).to have_content('Build')
+ expect(page).to have_content('Test')
+ expect(page).to have_content('Deploy')
+ expect(page).to have_content('Retry')
+ expect(page).to have_content('Cancel running')
+ end
+
+ it 'shows Pipeline tab pane as active' do
+ visit_pipeline
+
+ expect(page).to have_css('#js-tab-pipeline.active')
+ end
+
+ it 'shows link to the pipeline ref' do
+ visit_pipeline
+
+ expect(page).to have_link(pipeline.ref)
+ end
+
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for #{pipeline.ref}")
+ expect(page).to have_link(pipeline.ref,
+ href: project_commits_path(pipeline.project, pipeline.ref))
+ end
+ end
+
+ describe 'related merge requests' do
+ context 'when there are no related merge requests' do
+ it 'shows a "no related merge requests" message' do
+ visit_pipeline
+
+ within '.related-merge-request-info' do
+ expect(page).to have_content('No related merge requests found.')
+ end
+ end
+ end
+
+ context 'when there is one related merge request' do
+ let!(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref)
+ end
+
+ it 'shows a link to the merge request' do
+ visit_pipeline
+
+ within '.related-merge-requests' do
+ expect(page).to have_content('1 related merge request: ')
+ expect(page).to have_selector('.js-truncated-mr-list')
+ expect(page).to have_link("#{merge_request.to_reference} #{merge_request.title}")
+
+ expect(page).not_to have_selector('.js-full-mr-list')
+ expect(page).not_to have_selector('.text-expander')
+ end
+ end
+ end
+
+ context 'when there are two related merge requests' do
+ let!(:merge_request1) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref)
+ end
+
+ let!(:merge_request2) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: pipeline.ref,
+ target_branch: 'fix')
+ end
+
+ it 'links to the most recent related merge request' do
+ visit_pipeline
+
+ within '.related-merge-requests' do
+ expect(page).to have_content('2 related merge requests: ')
+ expect(page).to have_link("#{merge_request2.to_reference} #{merge_request2.title}")
+ expect(page).to have_selector('.text-expander')
+ expect(page).to have_selector('.js-full-mr-list', visible: false)
+ end
+ end
+
+ it 'expands to show links to all related merge requests' do
+ visit_pipeline
+
+ within '.related-merge-requests' do
+ find('.text-expander').click
+
+ expect(page).to have_selector('.js-full-mr-list', visible: true)
+
+ pipeline.all_merge_requests.map do |merge_request|
+ expect(page).to have_link(href: project_merge_request_path(project, merge_request))
+ end
+ end
+ end
+ end
+ end
+
+ describe 'pipelines details view' do
+ let!(:status) { create(:user_status, user: pipeline.user, emoji: 'smirk', message: 'Authoring this object') }
+
+ it 'pipeline header shows the user status and emoji' do
+ visit project_pipeline_path(project, pipeline)
+
+ within '[data-testid="ci-header-content"]' do
+ expect(page).to have_selector("[data-testid='#{status.message}']")
+ expect(page).to have_selector("[data-name='#{status.emoji}']")
+ end
+ end
+ end
+
+ describe 'pipeline graph' do
+ before do
+ visit_pipeline
+ end
+
+ context 'when pipeline has running builds' do
+ it 'shows a running icon and a cancel action for the running build' do
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ expect(page).to have_selector('.js-icon-cancel')
+ expect(page).to have_content('deploy')
+ end
+ end
+
+ it 'cancels the running build and shows retry button', :sidekiq_might_not_need_inline do
+ find('#ci-badge-deploy .ci-action-icon-container').click
+
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_css('.js-icon-retry')
+ end
+ end
+ end
+
+ context 'when pipeline has preparing builds' do
+ it 'shows a preparing icon and a cancel action' do
+ page.within('#ci-badge-prepare') do
+ expect(page).to have_selector('.js-ci-status-icon-preparing')
+ expect(page).to have_selector('.js-icon-cancel')
+ expect(page).to have_content('prepare')
+ end
+ end
+
+ it 'cancels the preparing build and shows retry button', :sidekiq_might_not_need_inline do
+ find('#ci-badge-deploy .ci-action-icon-container').click
+
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_css('.js-icon-retry')
+ end
+ end
+ end
+
+ context 'when pipeline has successful builds' do
+ it 'shows the success icon and a retry action for the successful build' do
+ page.within('#ci-badge-build') do
+ expect(page).to have_selector('.js-ci-status-icon-success')
+ expect(page).to have_content('build')
+ end
+
+ page.within('#ci-badge-build .ci-action-icon-container.js-icon-retry') do
+ expect(page).to have_selector('svg')
+ end
+ end
+
+ it 'is possible to retry the success job', :sidekiq_might_not_need_inline do
+ find('#ci-badge-build .ci-action-icon-container').click
+ wait_for_requests
+
+ expect(page).not_to have_content('Retry job')
+ within('.js-pipeline-header-container') do
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ end
+ end
+ end
+
+ context 'when pipeline has a delayed job' do
+ let(:project) { create(:project, :repository, group: group) }
+
+ it 'shows the scheduled icon and an unschedule action for the delayed job' do
+ page.within('#ci-badge-delayed-job') do
+ expect(page).to have_selector('.js-ci-status-icon-scheduled')
+ expect(page).to have_content('delayed-job')
+ end
+
+ page.within('#ci-badge-delayed-job .ci-action-icon-container.js-icon-time-out') do
+ expect(page).to have_selector('svg')
+ end
+ end
+
+ it 'unschedules the delayed job and shows play button as a manual job', :sidekiq_might_not_need_inline do
+ find('#ci-badge-delayed-job .ci-action-icon-container').click
+
+ page.within('#ci-badge-delayed-job') do
+ expect(page).to have_css('.js-icon-play')
+ end
+ end
+ end
+
+ context 'when pipeline has failed builds' do
+ it 'shows the failed icon and a retry action for the failed build' do
+ page.within('#ci-badge-test') do
+ expect(page).to have_selector('.js-ci-status-icon-failed')
+ expect(page).to have_content('test')
+ end
+
+ page.within('#ci-badge-test .ci-action-icon-container.js-icon-retry') do
+ expect(page).to have_selector('svg')
+ end
+ end
+
+ it 'is possible to retry the failed build', :sidekiq_might_not_need_inline do
+ find('#ci-badge-test .ci-action-icon-container').click
+ wait_for_requests
+
+ expect(page).not_to have_content('Retry job')
+ within('.js-pipeline-header-container') do
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ end
+ end
+
+ it 'includes the failure reason' do
+ page.within('#ci-badge-test') do
+ build_link = page.find('.js-pipeline-graph-job-link')
+ expect(build_link['title']).to eq('test - failed - (unknown failure)')
+ end
+ end
+ end
+
+ context 'when pipeline has manual jobs' do
+ it 'shows the skipped icon and a play action for the manual build' do
+ page.within('#ci-badge-manual-build') do
+ expect(page).to have_selector('.js-ci-status-icon-manual')
+ expect(page).to have_content('manual')
+ end
+
+ page.within('#ci-badge-manual-build .ci-action-icon-container.js-icon-play') do
+ expect(page).to have_selector('svg')
+ end
+ end
+
+ it 'is possible to play the manual job', :sidekiq_might_not_need_inline do
+ find('#ci-badge-manual-build .ci-action-icon-container').click
+ wait_for_requests
+
+ expect(page).not_to have_content('Play job')
+ within('.js-pipeline-header-container') do
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ end
+ end
+ end
+
+ context 'when pipeline has external job' do
+ it 'shows the success icon and the generic comit status build' do
+ expect(page).to have_selector('.js-ci-status-icon-success')
+ expect(page).to have_content('jenkins')
+ expect(page).to have_link('jenkins', href: 'http://gitlab.com/status')
+ end
+ end
+ end
+
+ context 'when the pipeline has manual stage' do
+ before do
+ create(:ci_build, :manual, pipeline: pipeline, stage_idx: 10, stage: 'publish', name: 'CentOS')
+ create(:ci_build, :manual, pipeline: pipeline, stage_idx: 10, stage: 'publish', name: 'Debian')
+ create(:ci_build, :manual, pipeline: pipeline, stage_idx: 10, stage: 'publish', name: 'OpenSUDE')
+
+ # force to update stages statuses
+ Ci::ProcessPipelineService.new(pipeline).execute
+
+ visit_pipeline
+ end
+
+ it 'displays play all button' do
+ expect(page).to have_selector('.js-stage-action')
+ end
+ end
+
+ context 'page tabs' do
+ before do
+ visit_pipeline
+ end
+
+ it 'shows Pipeline, Jobs, DAG and Failed Jobs tabs with link' do
+ expect(page).to have_link('Pipeline')
+ expect(page).to have_link('Jobs')
+ expect(page).to have_link('Needs')
+ expect(page).to have_link('Failed Jobs')
+ end
+
+ it 'shows counter in Jobs tab' do
+ expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
+ end
+
+ it 'shows Pipeline tab as active' do
+ expect(page).to have_css('.js-pipeline-tab-link .active')
+ end
+
+ context 'without permission to access builds' do
+ let(:project) { create(:project, :public, :repository, public_builds: false) }
+ let(:role) { :guest }
+
+ it 'does not show the pipeline details page' do
+ expect(page).to have_content('Not Found')
+ end
+ end
+ end
+
+ context 'retrying jobs' do
+ before do
+ visit_pipeline
+ end
+
+ it { expect(page).not_to have_content('retried') }
+
+ context 'when retrying' do
+ before do
+ find('[data-testid="retryPipeline"]').click
+ wait_for_requests
+ end
+
+ it 'does not show a "Retry" button', :sidekiq_might_not_need_inline do
+ expect(page).not_to have_content('Retry')
+ end
+
+ it 'shows running status in pipeline header', :sidekiq_might_not_need_inline do
+ within('.js-pipeline-header-container') do
+ expect(page).to have_selector('.js-ci-status-icon-running')
+ end
+ end
+ end
+ end
+
+ context 'canceling jobs' do
+ before do
+ visit_pipeline
+ end
+
+ it { expect(page).not_to have_selector('.ci-canceled') }
+
+ context 'when canceling' do
+ before do
+ click_on 'Cancel running'
+ end
+
+ it 'does not show a "Cancel running" button', :sidekiq_might_not_need_inline do
+ expect(page).not_to have_content('Cancel running')
+ end
+ end
+ end
+
+ context 'when user can not delete' do
+ before do
+ visit_pipeline
+ end
+
+ it { expect(page).not_to have_button('Delete') }
+ end
+
+ context 'when deleting' do
+ before do
+ group.add_owner(user)
+
+ visit_pipeline
+
+ click_button 'Delete'
+ click_button 'Delete pipeline'
+ end
+
+ it 'redirects to pipeline overview page', :sidekiq_inline do
+ expect(page).to have_content('The pipeline has been deleted')
+ expect(page).to have_current_path(project_pipelines_path(project), ignore_query: true)
+ end
+ end
+
+ context 'when pipeline ref does not exist in repository anymore' do
+ let(:pipeline) do
+ create(:ci_empty_pipeline, project: project,
+ ref: 'non-existent',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit_pipeline
+ end
+
+ it 'does not render link to the pipeline ref' do
+ expect(page).not_to have_link(pipeline.ref)
+ expect(page).to have_content(pipeline.ref)
+ end
+
+ it 'does not render render raw HTML to the pipeline ref' do
+ page.within '.pipeline-info' do
+ expect(page).not_to have_content('<span class="ref-name"')
+ end
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source branch does not exist' do
+ before do
+ project.repository.rm_branch(user, merge_request.source_branch)
+ end
+
+ it 'does not link to the source branch commit path' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).not_to have_link(merge_request.source_branch)
+ expect(page).to have_content(merge_request.source_branch)
+ end
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information', :sidekiq_might_not_need_inline do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:project) { create(:project, :repository, group: group) }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: project.commit.id)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ before do
+ pipeline.update!(user: user)
+ end
+
+ it 'shows the pipeline information' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+
+ context 'when target branch does not exist' do
+ before do
+ project.repository.rm_branch(user, merge_request.target_branch)
+ end
+
+ it 'does not link to the target branch commit path' do
+ visit_pipeline
+
+ within '.pipeline-info' do
+ expect(page).not_to have_link(merge_request.target_branch)
+ expect(page).to have_content(merge_request.target_branch)
+ end
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information', :sidekiq_might_not_need_inline do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+ end
+ end
+ end
+
+ context 'when user does not have access to read jobs' do
+ before do
+ project.update!(public_builds: false)
+ end
+
+ describe 'GET /:project/-/pipelines/:id' do
+ include_context 'pipeline builds'
+
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows the pipeline graph' do
+ expect(page).to have_selector('.js-pipeline-graph')
+ expect(page).to have_content('Build')
+ expect(page).to have_content('Test')
+ expect(page).to have_content('Deploy')
+ expect(page).to have_content('Retry')
+ expect(page).to have_content('Cancel running')
+ end
+
+ it 'does not link to job' do
+ expect(page).not_to have_selector('.js-pipeline-graph-job-link')
+ end
+ end
+ end
+
+ context 'when a bridge job exists' do
+ include_context 'pipeline builds'
+
+ let(:project) { create(:project, :repository) }
+ let(:downstream) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ let!(:bridge) do
+ create(:ci_bridge, pipeline: pipeline,
+ name: 'cross-build',
+ user: user,
+ downstream: downstream)
+ end
+
+ describe 'GET /:project/-/pipelines/:id' do
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows the pipeline with a bridge job' do
+ expect(page).to have_selector('.js-pipeline-graph')
+ expect(page).to have_content('cross-build')
+ end
+
+ context 'when a scheduled pipeline is created by a blocked user' do
+ let(:project) { create(:project, :repository) }
+
+ let(:schedule) do
+ create(:ci_pipeline_schedule,
+ project: project,
+ owner: project.first_owner,
+ description: 'blocked user schedule'
+ ).tap do |schedule|
+ schedule.update_column(:next_run_at, 1.minute.ago)
+ end
+ end
+
+ before do
+ schedule.owner.block!
+
+ begin
+ PipelineScheduleWorker.new.perform
+ rescue Ci::CreatePipelineService::CreateError
+ # Do nothing, assert view code after the Pipeline failed to create.
+ end
+ end
+
+ it 'displays the PipelineSchedule in an inactive state' do
+ visit project_pipeline_schedules_path(project)
+ page.click_link('Inactive')
+
+ expect(page).to have_selector('table.ci-table > tbody > tr > td', text: 'blocked user schedule')
+ end
+
+ it 'does not create a new Pipeline' do
+ visit project_pipelines_path(project)
+
+ expect(page).not_to have_selector('.ci-table')
+ expect(schedule.last_pipeline).to be_nil
+ end
+ end
+ end
+
+ describe 'GET /:project/-/pipelines/:id/builds' do
+ before do
+ visit builds_project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows a bridge job on a list' do
+ expect(page).to have_content('cross-build')
+ expect(page).to have_content(bridge.id)
+ end
+ end
+ end
+
+ context 'when build requires resource', :sidekiq_inline do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:resource_group) { create(:ci_resource_group, project: project) }
+
+ let!(:test_job) do
+ create(:ci_build, :pending, stage: 'test', name: 'test',
+ stage_idx: 1, pipeline: pipeline, project: project)
+ end
+
+ let!(:deploy_job) do
+ create(:ci_build, :created, stage: 'deploy', name: 'deploy',
+ stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
+ end
+
+ describe 'GET /:project/-/pipelines/:id' do
+ subject { visit project_pipeline_path(project, pipeline) }
+
+ it 'shows deploy job as created' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('pending')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[0]) do
+ expect(page).to have_content('test')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-created')
+ end
+ end
+ end
+
+ context 'when test job succeeded' do
+ before do
+ test_job.success!
+ end
+
+ it 'shows deploy job as pending' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('running')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[0]) do
+ expect(page).to have_content('test')
+ expect(page).to have_css('.ci-status-icon-success')
+ end
+
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+ end
+ end
+ end
+
+ context 'when test job succeeded but there are no available resources' do
+ let(:another_job) { create(:ci_build, :running, project: project, resource_group: resource_group) }
+
+ before do
+ resource_group.assign_resource_to(another_job)
+ test_job.success!
+ end
+
+ it 'shows deploy job as waiting for resource' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('waiting')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-waiting-for-resource')
+ end
+ end
+ end
+
+ context 'when resource is released from another job' do
+ before do
+ another_job.success!
+ end
+
+ it 'shows deploy job as pending' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('running')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-pending')
+ end
+ end
+ end
+ end
+
+ context 'when deploy job is a bridge to trigger a downstream pipeline' do
+ let!(:deploy_job) do
+ create(:ci_bridge, :created, stage: 'deploy', name: 'deploy',
+ stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
+ end
+
+ it 'shows deploy job as waiting for resource' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('waiting')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-waiting-for-resource')
+ end
+ end
+ end
+ end
+
+ context 'when deploy job is a bridge to trigger a downstream pipeline' do
+ let!(:deploy_job) do
+ create(:ci_bridge, :created, stage: 'deploy', name: 'deploy',
+ stage_idx: 2, pipeline: pipeline, project: project, resource_group: resource_group)
+ end
+
+ it 'shows deploy job as waiting for resource' do
+ subject
+
+ within('.js-pipeline-header-container') do
+ expect(page).to have_content('waiting')
+ end
+
+ within('.js-pipeline-graph') do
+ within(all('[data-testid="stage-column"]')[1]) do
+ expect(page).to have_content('deploy')
+ expect(page).to have_css('.ci-status-icon-waiting-for-resource')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe 'GET /:project/-/pipelines/:id/dag' do
+ include_context 'pipeline builds'
+
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
+
+ before do
+ visit dag_project_pipeline_path(project, pipeline)
+ end
+
+ it 'shows DAG tab pane as active' do
+ expect(page).to have_css('#js-tab-dag.active', visible: false)
+ end
+
+ context 'page tabs' do
+ it 'shows Pipeline, Jobs and DAG tabs with link' do
+ expect(page).to have_link('Pipeline')
+ expect(page).to have_link('Jobs')
+ expect(page).to have_link('DAG')
+ end
+
+ it 'shows counter in Jobs tab' do
+ expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
+ end
+
+ it 'shows DAG tab as active' do
+ expect(page).to have_css('li.js-dag-tab-link .active')
+ end
+ end
+ end
+
+ context 'when user sees pipeline flags in a pipeline detail page' do
+ let_it_be(:project) { create(:project, :repository) }
+
+ context 'when pipeline is latest' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates it is the latest build' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'latest'
+ end
+ end
+ end
+
+ context 'when pipeline has configuration errors' do
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :invalid,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates errors' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'yaml invalid'
+ end
+ end
+
+ it 'contains badge with tooltip which contains error' do
+ expect(pipeline).to have_yaml_errors
+
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.yaml_errors}"]})
+ end
+ end
+
+ it 'contains badge that indicates failure reason' do
+ expect(page).to have_content 'error'
+ end
+
+ it 'contains badge with tooltip which contains failure reason' do
+ expect(pipeline.failure_reason?).to eq true
+
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.present.failure_reason}"]})
+ end
+ end
+
+ it 'contains a pipeline header with title' do
+ expect(page).to have_content "Pipeline ##{pipeline.id}"
+ end
+ end
+
+ context 'when pipeline is stuck' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline)
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates being stuck' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'stuck'
+ end
+ end
+ end
+
+ context 'when pipeline uses auto devops' do
+ include_context 'pipeline builds'
+
+ let(:project) { create(:project, :repository, auto_devops_attributes: { enabled: true }) }
+ let(:pipeline) do
+ create(:ci_pipeline,
+ :auto_devops_source,
+ project: project,
+ ref: 'master',
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates using auto devops' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'Auto DevOps'
+ end
+ end
+ end
+
+ context 'when pipeline runs in a merge request context' do
+ include_context 'pipeline builds'
+
+ let(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: merge_request.source_project,
+ ref: 'feature',
+ sha: merge_request.diff_head_sha,
+ user: user,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ before do
+ visit project_pipeline_path(project, pipeline)
+ end
+
+ it 'contains badge that indicates detached merge request pipeline' do
+ page.within(all('.well-segment')[1]) do
+ expect(page).to have_content 'merge request'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/pipelines/legacy_pipelines_spec.rb b/spec/features/projects/pipelines/legacy_pipelines_spec.rb
new file mode 100644
index 00000000000..3f89e344c51
--- /dev/null
+++ b/spec/features/projects/pipelines/legacy_pipelines_spec.rb
@@ -0,0 +1,847 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Pipelines', :js do
+ include ProjectForksHelper
+ include Spec::Support::Helpers::ModalHelpers
+
+ let(:project) { create(:project) }
+ let(:expected_detached_mr_tag) {'merge request'}
+
+ context 'when user is logged in' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.add_developer(user)
+ project.update!(auto_devops_attributes: { enabled: false })
+
+ stub_feature_flags(pipeline_tabs_vue: false)
+ end
+
+ describe 'GET /:project/-/pipelines' do
+ let(:project) { create(:project, :repository) }
+
+ let!(:pipeline) do
+ create(
+ :ci_empty_pipeline,
+ project: project,
+ ref: 'master',
+ status: 'running',
+ sha: project.commit.id
+ )
+ end
+
+ context 'scope' do
+ before do
+ create(:ci_empty_pipeline, status: 'pending', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'running', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'created', project: project, sha: project.commit.id, ref: 'master')
+ create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master')
+ end
+
+ [:all, :running, :pending, :finished, :branches].each do |scope|
+ context "when displaying #{scope}" do
+ before do
+ visit_project_pipelines(scope: scope)
+ end
+
+ it 'contains pipeline commit short SHA' do
+ expect(page).to have_content(pipeline.short_sha)
+ end
+
+ it 'contains branch name' do
+ expect(page).to have_content(pipeline.ref)
+ end
+ end
+ end
+ end
+
+ context 'header tabs' do
+ before do
+ visit project_pipelines_path(project)
+ wait_for_requests
+ end
+
+ it 'shows a tab for All pipelines and count' do
+ expect(page.find('.js-pipelines-tab-all').text).to include('All')
+ expect(page.find('.js-pipelines-tab-all .badge').text).to include('1')
+ end
+
+ it 'shows a tab for Finished pipelines and count' do
+ expect(page.find('.js-pipelines-tab-finished').text).to include('Finished')
+ end
+
+ it 'shows a tab for Branches' do
+ expect(page.find('.js-pipelines-tab-branches').text).to include('Branches')
+ end
+
+ it 'shows a tab for Tags' do
+ expect(page.find('.js-pipelines-tab-tags').text).to include('Tags')
+ end
+
+ it 'updates content when tab is clicked' do
+ page.find('.js-pipelines-tab-finished').click
+ wait_for_requests
+ expect(page).to have_content('There are currently no finished pipelines.')
+ end
+ end
+
+ context 'navigation links' do
+ before do
+ visit project_pipelines_path(project)
+ wait_for_requests
+ end
+
+ it 'renders "CI lint" link' do
+ expect(page).to have_link('CI lint')
+ end
+
+ it 'renders "Run pipeline" link' do
+ expect(page).to have_link('Run pipeline')
+ end
+ end
+
+ context 'when pipeline is cancelable' do
+ let!(:build) do
+ create(:ci_build, pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before do
+ build.run
+ visit_project_pipelines
+ end
+
+ it 'indicates that pipeline can be canceled' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ expect(page).to have_selector('.ci-running')
+ end
+
+ context 'when canceling' do
+ before do
+ find('.js-pipelines-cancel-button').click
+ click_button 'Stop pipeline'
+ wait_for_requests
+ end
+
+ it 'indicated that pipelines was canceled', :sidekiq_might_not_need_inline do
+ expect(page).not_to have_selector('.js-pipelines-cancel-button')
+ expect(page).to have_selector('.ci-canceled')
+ end
+ end
+ end
+
+ context 'when pipeline is retryable', :sidekiq_might_not_need_inline do
+ let!(:build) do
+ create(:ci_build, pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before do
+ build.drop
+ visit_project_pipelines
+ end
+
+ it 'indicates that pipeline can be retried' do
+ expect(page).to have_selector('.js-pipelines-retry-button')
+ expect(page).to have_selector('.ci-failed')
+ end
+
+ context 'when retrying' do
+ before do
+ find('.js-pipelines-retry-button').click
+ wait_for_requests
+ end
+
+ it 'shows running pipeline that is not retryable' do
+ expect(page).not_to have_selector('.js-pipelines-retry-button')
+ expect(page).to have_selector('.ci-running')
+ end
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'detached merge request pipeline' do
+ it 'shows pipeline information without pipeline ref', :sidekiq_might_not_need_inline do
+ within '.pipeline-tags' do
+ expect(page).to have_content(expected_detached_mr_tag)
+
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'detached merge request pipeline'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'detached merge request pipeline'
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: target_project.commit.sha)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'Correct merge request pipeline information' do
+ it 'does not show detached tag for the pipeline, and shows the link of the merge request' \
+ 'and does not show the ref of the pipeline', :sidekiq_might_not_need_inline do
+ within '.pipeline-tags' do
+ expect(page).not_to have_content(expected_detached_mr_tag)
+
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'Correct merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'Correct merge request pipeline information'
+ end
+ end
+
+ context 'when pipeline has configuration errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, :invalid, project: project)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'contains badge that indicates errors' do
+ expect(page).to have_content 'yaml invalid'
+ end
+
+ it 'contains badge with tooltip which contains error' do
+ expect(pipeline).to have_yaml_errors
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.yaml_errors}"]})
+ end
+
+ it 'contains badge that indicates failure reason' do
+ expect(page).to have_content 'error'
+ end
+
+ it 'contains badge with tooltip which contains failure reason' do
+ expect(pipeline.failure_reason?).to eq true
+ expect(page).to have_selector(
+ %Q{span[title="#{pipeline.present.failure_reason}"]})
+ end
+ end
+
+ context 'with manual actions' do
+ let!(:manual) do
+ create(:ci_build, :manual,
+ pipeline: pipeline,
+ name: 'manual build',
+ stage: 'test')
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'has a dropdown with play button' do
+ expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
+ end
+
+ it 'has link to the manual action' do
+ find('[data-testid="pipelines-manual-actions-dropdown"]').click
+
+ expect(page).to have_button('manual build')
+ end
+
+ context 'when manual action was played' do
+ before do
+ find('[data-testid="pipelines-manual-actions-dropdown"]').click
+ click_button('manual build')
+ end
+
+ it 'enqueues manual action job' do
+ expect(page).to have_selector(
+ '[data-testid="pipelines-manual-actions-dropdown"] .gl-dropdown-toggle:disabled'
+ )
+ end
+ end
+ end
+
+ context 'when there is a delayed job' do
+ let!(:delayed_job) do
+ create(:ci_build, :scheduled,
+ pipeline: pipeline,
+ name: 'delayed job 1',
+ stage: 'test')
+ end
+
+ before do
+ stub_feature_flags(bootstrap_confirmation_modals: false)
+ visit_project_pipelines
+ end
+
+ it 'has a dropdown for actionable jobs' do
+ expect(page).to have_selector('[data-testid="pipelines-manual-actions-dropdown"] [data-testid="play-icon"]')
+ end
+
+ it "has link to the delayed job's action" do
+ find('[data-testid="pipelines-manual-actions-dropdown"]').click
+
+ time_diff = [0, delayed_job.scheduled_at - Time.zone.now].max
+ expect(page).to have_button('delayed job 1')
+ expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S"))
+ end
+
+ context 'when delayed job is expired already' do
+ let!(:delayed_job) do
+ create(:ci_build, :expired_scheduled,
+ pipeline: pipeline,
+ name: 'delayed job 1',
+ stage: 'test')
+ end
+
+ it "shows 00:00:00 as the remaining time" do
+ find('[data-testid="pipelines-manual-actions-dropdown"]').click
+
+ expect(page).to have_content("00:00:00")
+ end
+ end
+
+ context 'when user played a delayed job immediately' do
+ before do
+ find('[data-testid="pipelines-manual-actions-dropdown"]').click
+ accept_gl_confirm do
+ click_button 'delayed job 1'
+ end
+ wait_for_requests
+ end
+
+ it 'enqueues the delayed job', :js do
+ expect(delayed_job.reload).to be_pending
+ end
+ end
+ end
+
+ context 'for generic statuses' do
+ context 'when preparing' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ status: 'preparing', project: project)
+ end
+
+ let!(:status) do
+ create(:generic_commit_status,
+ :preparing, pipeline: pipeline)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'is cancelable' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ end
+
+ it 'shows the pipeline as preparing' do
+ expect(page).to have_selector('.ci-preparing')
+ end
+ end
+
+ context 'when running' do
+ let!(:running) do
+ create(:generic_commit_status,
+ status: 'running',
+ pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'is cancelable' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ end
+
+ it 'has pipeline running' do
+ expect(page).to have_selector('.ci-running')
+ end
+
+ context 'when canceling' do
+ before do
+ find('.js-pipelines-cancel-button').click
+ click_button 'Stop pipeline'
+ end
+
+ it 'indicates that pipeline was canceled', :sidekiq_might_not_need_inline do
+ expect(page).not_to have_selector('.js-pipelines-cancel-button')
+ expect(page).to have_selector('.ci-canceled')
+ end
+ end
+ end
+
+ context 'when failed' do
+ let!(:status) do
+ create(:generic_commit_status, :pending,
+ pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before do
+ status.drop
+ visit_project_pipelines
+ end
+
+ it 'is not retryable' do
+ expect(page).not_to have_selector('.js-pipelines-retry-button')
+ end
+
+ it 'has failed pipeline', :sidekiq_might_not_need_inline do
+ expect(page).to have_selector('.ci-failed')
+ end
+ end
+ end
+
+ context 'downloadable pipelines' do
+ context 'with artifacts' do
+ let!(:with_artifacts) do
+ build = create(:ci_build, :success,
+ pipeline: pipeline,
+ name: 'rspec tests',
+ stage: 'test')
+
+ create(:ci_job_artifact, :codequality, job: build)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'has artifacts dropdown' do
+ expect(page).to have_selector('[data-testid="pipeline-multi-actions-dropdown"]')
+ end
+ end
+
+ context 'with artifacts expired' do
+ let!(:with_artifacts_expired) do
+ create(:ci_build, :expired, :success,
+ pipeline: pipeline,
+ name: 'rspec',
+ stage: 'test')
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it { expect(page).not_to have_selector('[data-testid="artifact-item"]') }
+ end
+
+ context 'without artifacts' do
+ let!(:without_artifacts) do
+ create(:ci_build, :success,
+ pipeline: pipeline,
+ name: 'rspec',
+ stage: 'test')
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it { expect(page).not_to have_selector('[data-testid="artifact-item"]') }
+ end
+
+ context 'with trace artifact' do
+ before do
+ create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+
+ visit_project_pipelines
+ end
+
+ it 'does not show trace artifact as artifacts' do
+ expect(page).not_to have_selector('[data-testid="artifact-item"]')
+ end
+ end
+ end
+
+ context 'mini pipeline graph' do
+ let!(:build) do
+ create(:ci_build, :pending, pipeline: pipeline,
+ stage: 'build',
+ name: 'build')
+ end
+
+ dropdown_selector = '[data-testid="mini-pipeline-graph-dropdown"]'
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'renders a mini pipeline graph' do
+ expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
+ expect(page).to have_selector(dropdown_selector)
+ end
+
+ context 'when clicking a stage badge' do
+ it 'opens a dropdown' do
+ find(dropdown_selector).click
+
+ expect(page).to have_link build.name
+ end
+
+ it 'is possible to cancel pending build' do
+ find(dropdown_selector).click
+ find('.js-ci-action').click
+ wait_for_requests
+
+ expect(build.reload).to be_canceled
+ end
+ end
+
+ context 'for a failed pipeline' do
+ let!(:build) do
+ create(:ci_build, :failed, pipeline: pipeline,
+ stage: 'build',
+ name: 'build')
+ end
+
+ it 'displays the failure reason' do
+ find(dropdown_selector).click
+
+ within('.js-builds-dropdown-list') do
+ build_element = page.find('.mini-pipeline-graph-dropdown-item')
+ expect(build_element['title']).to eq('build - failed - (unknown failure)')
+ end
+ end
+ end
+ end
+
+ context 'with pagination' do
+ before do
+ allow(Ci::Pipeline).to receive(:default_per_page).and_return(1)
+ create(:ci_empty_pipeline, project: project)
+ end
+
+ it 'renders pagination' do
+ visit project_pipelines_path(project)
+ wait_for_requests
+
+ expect(page).to have_selector('.gl-pagination')
+ end
+
+ it 'renders second page of pipelines' do
+ visit project_pipelines_path(project, page: '2')
+ wait_for_requests
+
+ expect(page).to have_selector('.gl-pagination .page-link', count: 4)
+ end
+
+ it 'shows updated content' do
+ visit project_pipelines_path(project)
+ wait_for_requests
+ page.find('.page-link.next-page-item').click
+
+ expect(page).to have_selector('.gl-pagination .page-link', count: 4)
+ end
+ end
+
+ context 'with pipeline key selection' do
+ before do
+ visit project_pipelines_path(project)
+ wait_for_requests
+ end
+
+ it 'changes the Pipeline ID column for Pipeline IID' do
+ page.find('[data-testid="pipeline-key-dropdown"]').click
+
+ within '.gl-new-dropdown-contents' do
+ dropdown_options = page.find_all '.gl-new-dropdown-item'
+
+ dropdown_options[1].click
+ end
+
+ expect(page.find('[data-testid="pipeline-th"]')).to have_content 'Pipeline'
+ expect(page.find('[data-testid="pipeline-url-link"]')).to have_content "##{pipeline.iid}"
+ end
+ end
+ end
+
+ describe 'GET /:project/-/pipelines/show' do
+ let(:project) { create(:project, :repository) }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline,
+ project: project,
+ sha: project.commit.id,
+ user: user)
+ end
+
+ before do
+ create_build('build', 0, 'build', :success)
+ create_build('test', 1, 'rspec 0:2', :pending)
+ create_build('test', 1, 'rspec 1:2', :running)
+ create_build('test', 1, 'spinach 0:2', :created)
+ create_build('test', 1, 'spinach 1:2', :created)
+ create_build('test', 1, 'audit', :created)
+ create_build('deploy', 2, 'production', :created)
+
+ create(
+ :generic_commit_status,
+ pipeline: pipeline,
+ stage: 'external',
+ name: 'jenkins',
+ stage_idx: 3,
+ ref: 'master'
+ )
+
+ visit project_pipeline_path(project, pipeline)
+ wait_for_requests
+ end
+
+ it 'shows a graph with grouped stages' do
+ expect(page).to have_css('.js-pipeline-graph')
+
+ # header
+ expect(page).to have_text("##{pipeline.id}")
+ expect(page).to have_selector(%Q(img[src="#{pipeline.user.avatar_url}"]))
+ expect(page).to have_link(pipeline.user.name, href: user_path(pipeline.user))
+
+ # stages
+ expect(page).to have_text('Build')
+ expect(page).to have_text('Test')
+ expect(page).to have_text('Deploy')
+ expect(page).to have_text('External')
+
+ # builds
+ expect(page).to have_text('rspec')
+ expect(page).to have_text('spinach')
+ expect(page).to have_text('rspec')
+ expect(page).to have_text('production')
+ expect(page).to have_text('jenkins')
+ end
+
+ def create_build(stage, stage_idx, name, status)
+ create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status)
+ end
+ end
+
+ describe 'POST /:project/-/pipelines' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ visit new_project_pipeline_path(project)
+ end
+
+ context 'for valid commit', :js do
+ before do
+ click_button project.default_branch
+ wait_for_requests
+
+ find('p', text: 'master').click
+ wait_for_requests
+ end
+
+ context 'with gitlab-ci.yml', :js do
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ it 'creates a new pipeline' do
+ expect do
+ click_on 'Run pipeline'
+ wait_for_requests
+ end
+ .to change { Ci::Pipeline.count }.by(1)
+
+ expect(Ci::Pipeline.last).to be_web
+ end
+
+ context 'when variables are specified' do
+ it 'creates a new pipeline with variables' do
+ page.within(find("[data-testid='ci-variable-row']")) do
+ find("[data-testid='pipeline-form-ci-variable-key']").set('key_name')
+ find("[data-testid='pipeline-form-ci-variable-value']").set('value')
+ end
+
+ expect do
+ click_on 'Run pipeline'
+ wait_for_requests
+ end
+ .to change { Ci::Pipeline.count }.by(1)
+
+ expect(Ci::Pipeline.last.variables.map { |var| var.slice(:key, :secret_value) })
+ .to eq [{ key: "key_name", secret_value: "value" }.with_indifferent_access]
+ end
+ end
+ end
+
+ context 'without gitlab-ci.yml' do
+ before do
+ click_on 'Run pipeline'
+ wait_for_requests
+ end
+
+ it { expect(page).to have_content('Missing CI config file') }
+ it 'creates a pipeline after first request failed and a valid gitlab-ci.yml file' \
+ 'is available when trying again' do
+ stub_ci_pipeline_to_return_yaml_file
+
+ expect do
+ click_on 'Run pipeline'
+ wait_for_requests
+ end
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+ end
+ end
+ end
+
+ describe 'Reset runner caches' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master')
+ project.add_maintainer(user)
+ visit project_pipelines_path(project)
+ end
+
+ it 'has a clear caches button' do
+ expect(page).to have_button 'Clear runner caches'
+ end
+
+ describe 'user clicks the button' do
+ context 'when project already has jobs_cache_index' do
+ before do
+ project.update!(jobs_cache_index: 1)
+ end
+
+ it 'increments jobs_cache_index' do
+ click_button 'Clear runner caches'
+ wait_for_requests
+ expect(page.find('[data-testid="alert-info"]')).to have_content 'Project cache successfully reset.'
+ end
+ end
+
+ context 'when project does not have jobs_cache_index' do
+ it 'sets jobs_cache_index to 1' do
+ click_button 'Clear runner caches'
+ wait_for_requests
+ expect(page.find('[data-testid="alert-info"]')).to have_content 'Project cache successfully reset.'
+ end
+ end
+ end
+ end
+
+ describe 'Run Pipelines' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ visit new_project_pipeline_path(project)
+ end
+
+ describe 'new pipeline page' do
+ it 'has field to add a new pipeline' do
+ expect(page).to have_selector('[data-testid="ref-select"]')
+ expect(find('[data-testid="ref-select"]')).to have_content project.default_branch
+ expect(page).to have_content('Run for')
+ end
+ end
+
+ describe 'find pipelines' do
+ it 'shows filtered pipelines', :js do
+ click_button project.default_branch
+
+ page.within '[data-testid="ref-select"]' do
+ find('[data-testid="search-refs"]').native.send_keys('fix')
+
+ page.within '.gl-new-dropdown-contents' do
+ expect(page).to have_content('fix')
+ end
+ end
+ end
+ end
+ end
+
+ describe 'Empty State' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ visit project_pipelines_path(project)
+ end
+
+ it 'renders empty state' do
+ expect(page).to have_content 'Try test template'
+ end
+ end
+ end
+
+ context 'when user is not logged in' do
+ before do
+ project.update!(auto_devops_attributes: { enabled: false })
+ visit project_pipelines_path(project)
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :public, :repository) }
+
+ context 'without pipelines' do
+ it { expect(page).to have_content 'This project is not currently set up to run pipelines.' }
+ end
+ end
+
+ context 'when project is private' do
+ let(:project) { create(:project, :private, :repository) }
+
+ it 'redirects the user to sign_in and displays the flash alert' do
+ expect(page).to have_content 'You need to sign in'
+ expect(page).to have_current_path("/users/sign_in")
+ end
+ end
+ end
+
+ def visit_project_pipelines(**query)
+ visit project_pipelines_path(project, query)
+ wait_for_requests
+ end
+end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index c6a2ccb3e8c..9eda05f695d 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -15,7 +15,6 @@ RSpec.describe 'Pipeline', :js do
before do
sign_in(user)
project.add_role(user, role)
- stub_feature_flags(pipeline_tabs_vue: false)
end
shared_context 'pipeline builds' do
@@ -80,12 +79,6 @@ RSpec.describe 'Pipeline', :js do
expect(page).to have_content('Cancel running')
end
- it 'shows Pipeline tab pane as active' do
- visit_pipeline
-
- expect(page).to have_css('#js-tab-pipeline.active')
- end
-
it 'shows link to the pipeline ref' do
visit_pipeline
@@ -504,7 +497,6 @@ RSpec.describe 'Pipeline', :js do
context 'page tabs' do
before do
- stub_feature_flags(pipeline_tabs_vue: false)
visit_pipeline
end
@@ -516,13 +508,10 @@ RSpec.describe 'Pipeline', :js do
end
it 'shows counter in Jobs tab' do
+ skip('Enable in jobs `pipeline_tabs_vue` MR')
expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
end
- it 'shows Pipeline tab as active' do
- expect(page).to have_css('.js-pipeline-tab-link .active')
- end
-
context 'without permission to access builds' do
let(:project) { create(:project, :public, :repository, public_builds: false) }
let(:role) { :guest }
@@ -900,6 +889,7 @@ RSpec.describe 'Pipeline', :js do
describe 'GET /:project/-/pipelines/:id/builds' do
before do
+ stub_feature_flags(pipeline_tabs_vue: false)
visit builds_project_pipeline_path(project, pipeline)
end
@@ -1068,15 +1058,7 @@ RSpec.describe 'Pipeline', :js do
expect(page).to have_button('Play')
end
- it 'shows jobs tab pane as active' do
- expect(page).to have_css('#js-tab-builds.active')
- end
-
context 'page tabs' do
- before do
- stub_feature_flags(pipeline_tabs_vue: false)
- end
-
it 'shows Pipeline, Jobs and DAG tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
@@ -1084,12 +1066,9 @@ RSpec.describe 'Pipeline', :js do
end
it 'shows counter in Jobs tab' do
+ skip('unskip when jobs tab is implemented with ff `pipeline_tabs_vue`')
expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
end
-
- it 'shows Jobs tab as active' do
- expect(page).to have_css('li.js-builds-tab-link .active')
- end
end
context 'retrying jobs' do
@@ -1147,14 +1126,14 @@ RSpec.describe 'Pipeline', :js do
end
describe 'GET /:project/-/pipelines/:id/failures' do
- before do
- stub_feature_flags(pipeline_tabs_vue: false)
- end
-
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: '1234') }
let(:pipeline_failures_page) { failures_project_pipeline_path(project, pipeline) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline) }
+ before do
+ stub_feature_flags(pipeline_tabs_vue: false)
+ end
+
subject { visit pipeline_failures_page }
context 'with failed build' do
@@ -1162,13 +1141,6 @@ RSpec.describe 'Pipeline', :js do
failed_build.trace.set('4 examples, 1 failure')
end
- it 'shows jobs tab pane as active' do
- subject
-
- expect(page).to have_content('Failed Jobs')
- expect(page).to have_css('#js-tab-failures.active')
- end
-
it 'lists failed builds' do
subject
@@ -1246,13 +1218,6 @@ RSpec.describe 'Pipeline', :js do
end
context 'when missing build logs' do
- it 'shows jobs tab pane as active' do
- subject
-
- expect(page).to have_content('Failed Jobs')
- expect(page).to have_css('#js-tab-failures.active')
- end
-
it 'lists failed builds' do
subject
@@ -1289,11 +1254,17 @@ RSpec.describe 'Pipeline', :js do
failed_build.update!(status: :success)
end
+ it 'does not show the failure tab' do
+ skip('unskip when the failure tab has been implemented in ff `pipeline_tabs_vue`')
+ subject
+
+ expect(page).not_to have_content('Failed Jobs')
+ end
+
it 'displays the pipeline graph' do
subject
expect(page).to have_current_path(pipeline_path(pipeline), ignore_query: true)
- expect(page).not_to have_content('Failed Jobs')
expect(page).to have_selector('.js-pipeline-graph')
end
end
@@ -1307,27 +1278,14 @@ RSpec.describe 'Pipeline', :js do
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id) }
before do
- stub_feature_flags(pipeline_tabs_vue: false)
visit dag_project_pipeline_path(project, pipeline)
end
- it 'shows DAG tab pane as active' do
- expect(page).to have_css('#js-tab-dag.active', visible: false)
- end
-
context 'page tabs' do
it 'shows Pipeline, Jobs and DAG tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
- expect(page).to have_link('DAG')
- end
-
- it 'shows counter in Jobs tab' do
- expect(page.find('.js-builds-counter').text).to eq(pipeline.total_size.to_s)
- end
-
- it 'shows DAG tab as active' do
- expect(page).to have_css('li.js-dag-tab-link .active')
+ expect(page).to have_link('Needs')
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 99ecb5efe2f..a18bf7c5caf 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -623,7 +623,6 @@ RSpec.describe 'Pipelines', :js do
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3, ref: 'master')
- stub_feature_flags(pipeline_tabs_vue: false)
visit project_pipeline_path(project, pipeline)
wait_for_requests
end
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
index 9cff08c7f29..bcf3defe9c6 100644
--- a/spec/features/user_sorts_things_spec.rb
+++ b/spec/features/user_sorts_things_spec.rb
@@ -6,7 +6,7 @@ require "spec_helper"
# to check if the sorting option set by user is being kept persisted while going through pages.
# The `it`s are named here by convention `starting point -> some pages -> final point`.
# All those specs are moved out to this spec intentionally to keep them all in one place.
-RSpec.describe "User sorts things" do
+RSpec.describe "User sorts things", :js do
include Spec::Support::Helpers::Features::SortingHelpers
include DashboardHelper
@@ -22,12 +22,12 @@ RSpec.describe "User sorts things" do
sign_in(current_user)
end
- it "issues -> project home page -> issues", :js do
- sort_option = 'Updated date'
+ it "issues -> project home page -> issues" do
+ sort_option = s_('SortOptions|Updated date')
visit(project_issues_path(project))
- click_button 'Created date'
+ click_button s_('SortOptions|Created date')
click_button sort_option
visit(project_path(project))
@@ -37,11 +37,11 @@ RSpec.describe "User sorts things" do
end
it "merge requests -> dashboard merge requests" do
- sort_option = 'Updated date'
+ sort_option = s_('SortOptions|Updated date')
visit(project_merge_requests_path(project))
- sort_by(sort_option)
+ pajamas_sort_by(sort_option)
visit(assigned_mrs_dashboard_path)
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index a8182bcae51..45639f4c948 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -11,9 +11,6 @@ settings:
import/resolver:
jest:
jestConfigFile: 'jest.config.js'
-globals:
- loadFixtures: false
- setFixtures: false
rules:
jest/expect-expect:
- off
diff --git a/spec/frontend/__helpers__/fixtures.js b/spec/frontend/__helpers__/fixtures.js
index d8054d32fae..a6f7b37161e 100644
--- a/spec/frontend/__helpers__/fixtures.js
+++ b/spec/frontend/__helpers__/fixtures.js
@@ -20,24 +20,15 @@ Did you run bin/rake frontend:fixtures?`,
return fs.readFileSync(absolutePath, 'utf8');
}
-/**
- * @deprecated Use `import` to load a JSON fixture instead.
- * See https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html#use-fixtures,
- * https://gitlab.com/gitlab-org/gitlab/-/issues/339346.
- */
-export const getJSONFixture = (relativePath) => JSON.parse(getFixture(relativePath));
-
export const resetHTMLFixture = () => {
document.head.innerHTML = '';
document.body.innerHTML = '';
};
-export const setHTMLFixture = (htmlContent, resetHook = afterEach) => {
+export const setHTMLFixture = (htmlContent) => {
document.body.innerHTML = htmlContent;
- resetHook(resetHTMLFixture);
};
-export const loadHTMLFixture = (relativePath, resetHook = afterEach) => {
- const fileContent = getFixture(relativePath);
- setHTMLFixture(fileContent, resetHook);
+export const loadHTMLFixture = (relativePath) => {
+ setHTMLFixture(getFixture(relativePath));
};
diff --git a/spec/frontend/__helpers__/shared_test_setup.js b/spec/frontend/__helpers__/shared_test_setup.js
index 7b5df18ee0f..011e1142c76 100644
--- a/spec/frontend/__helpers__/shared_test_setup.js
+++ b/spec/frontend/__helpers__/shared_test_setup.js
@@ -6,7 +6,6 @@ import 'jquery';
import Translate from '~/vue_shared/translate';
import setWindowLocation from './set_window_location_helper';
import { setGlobalDateToFakeDate } from './fake_date';
-import { loadHTMLFixture, setHTMLFixture } from './fixtures';
import { TEST_HOST } from './test_constants';
import * as customMatchers from './matchers';
@@ -28,12 +27,6 @@ Vue.config.productionTip = false;
Vue.use(Translate);
-// convenience wrapper for migration from Karma
-Object.assign(global, {
- loadFixtures: loadHTMLFixture,
- setFixtures: setHTMLFixture,
-});
-
const JQUERY_MATCHERS_TO_EXCLUDE = ['toHaveLength', 'toExist'];
// custom-jquery-matchers was written for an old Jest version, we need to make it compatible
diff --git a/spec/frontend/activities_spec.js b/spec/frontend/activities_spec.js
index 00519148b30..ebace21217a 100644
--- a/spec/frontend/activities_spec.js
+++ b/spec/frontend/activities_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable no-unused-expressions, no-prototype-builtins, no-new, no-shadow */
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import Activities from '~/activities';
import Pager from '~/pager';
@@ -38,11 +39,15 @@ describe('Activities', () => {
}
beforeEach(() => {
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
jest.spyOn(Pager, 'init').mockImplementation(() => {});
new Activities();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
for (let i = 0; i < filters.length; i += 1) {
((i) => {
describe(`when selecting ${getEventName(i)}`, () => {
diff --git a/spec/frontend/admin/applications/components/delete_application_spec.js b/spec/frontend/admin/applications/components/delete_application_spec.js
index 20119b64952..1a400a101b5 100644
--- a/spec/frontend/admin/applications/components/delete_application_spec.js
+++ b/spec/frontend/admin/applications/components/delete_application_spec.js
@@ -1,5 +1,6 @@
import { GlModal, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import DeleteApplication from '~/admin/applications/components/delete_application.vue';
const path = 'application/path/1';
@@ -22,7 +23,7 @@ describe('DeleteApplication', () => {
const findForm = () => wrapper.find('form');
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<button class="js-application-delete-button" data-path="${path}" data-name="${name}">Destroy</button>
`);
@@ -31,6 +32,7 @@ describe('DeleteApplication', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
describe('the modal component', () => {
diff --git a/spec/frontend/admin/users/new_spec.js b/spec/frontend/admin/users/new_spec.js
index 692c583dca8..5e5763822a8 100644
--- a/spec/frontend/admin/users/new_spec.js
+++ b/spec/frontend/admin/users/new_spec.js
@@ -4,6 +4,7 @@ import {
ID_USER_EXTERNAL,
ID_WARNING,
} from '~/admin/users/new';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
describe('admin/users/new', () => {
const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
@@ -13,7 +14,7 @@ describe('admin/users/new', () => {
let elWarningMessage;
beforeEach(() => {
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
setupInternalUserRegexHandler();
elExternal = document.getElementById(ID_USER_EXTERNAL);
@@ -23,6 +24,10 @@ describe('admin/users/new', () => {
elExternal.checked = true;
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const changeEmail = (val) => {
elUserEmail.value = val;
elUserEmail.dispatchEvent(new Event('input'));
diff --git a/spec/frontend/alert_handler_spec.js b/spec/frontend/alert_handler_spec.js
index 228053b1b2b..ba8e5bcb202 100644
--- a/spec/frontend/alert_handler_spec.js
+++ b/spec/frontend/alert_handler_spec.js
@@ -1,4 +1,4 @@
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initAlertHandler from '~/alert_handler';
describe('Alert Handler', () => {
@@ -25,6 +25,10 @@ describe('Alert Handler', () => {
initAlertHandler();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should render the alert', () => {
expect(findFirstAlert()).not.toBe(null);
});
@@ -41,6 +45,10 @@ describe('Alert Handler', () => {
initAlertHandler();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should render two alerts', () => {
expect(findAllAlerts()).toHaveLength(2);
});
@@ -57,6 +65,10 @@ describe('Alert Handler', () => {
initAlertHandler();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should render the banner', () => {
expect(findFirstBanner()).not.toBe(null);
});
@@ -78,6 +90,10 @@ describe('Alert Handler', () => {
initAlertHandler();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should render the banner', () => {
expect(findFirstAlert()).not.toBe(null);
});
diff --git a/spec/frontend/attention_requests/components/navigation_popover_spec.js b/spec/frontend/attention_requests/components/navigation_popover_spec.js
index d0231afbdc4..e4d53d5dbdb 100644
--- a/spec/frontend/attention_requests/components/navigation_popover_spec.js
+++ b/spec/frontend/attention_requests/components/navigation_popover_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlPopover, GlButton, GlSprintf, GlIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import NavigationPopover from '~/attention_requests/components/navigation_popover.vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
let wrapper;
@@ -29,13 +30,14 @@ function createComponent(provideData = {}, shouldShowCallout = true) {
describe('Attention requests navigation popover', () => {
beforeEach(() => {
- setFixtures('<div><div class="js-test-popover"></div><div class="js-test"></div></div>');
+ setHTMLFixture('<div><div class="js-test-popover"></div><div class="js-test"></div></div>');
dismiss = jest.fn();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ resetHTMLFixture();
});
it('hides popover if callout is disabled', () => {
diff --git a/spec/frontend/authentication/u2f/authenticate_spec.js b/spec/frontend/authentication/u2f/authenticate_spec.js
index 31782899ce4..3ae7fcf1c49 100644
--- a/spec/frontend/authentication/u2f/authenticate_spec.js
+++ b/spec/frontend/authentication/u2f/authenticate_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import U2FAuthenticate from '~/authentication/u2f/authenticate';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
@@ -9,7 +10,7 @@ describe('U2FAuthenticate', () => {
let component;
beforeEach(() => {
- loadFixtures('u2f/authenticate.html');
+ loadHTMLFixture('u2f/authenticate.html');
u2fDevice = new MockU2FDevice();
container = $('#js-authenticate-token-2fa');
component = new U2FAuthenticate(
@@ -23,6 +24,10 @@ describe('U2FAuthenticate', () => {
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('with u2f unavailable', () => {
let oldu2f;
diff --git a/spec/frontend/authentication/u2f/register_spec.js b/spec/frontend/authentication/u2f/register_spec.js
index 810396aa9fd..7ae3a2734cb 100644
--- a/spec/frontend/authentication/u2f/register_spec.js
+++ b/spec/frontend/authentication/u2f/register_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import U2FRegister from '~/authentication/u2f/register';
import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
@@ -9,13 +10,17 @@ describe('U2FRegister', () => {
let component;
beforeEach(() => {
- loadFixtures('u2f/register.html');
+ loadHTMLFixture('u2f/register.html');
u2fDevice = new MockU2FDevice();
container = $('#js-register-token-2fa');
component = new U2FRegister(container, {});
return component.start();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('allows registering a U2F device', () => {
const setupButton = container.find('#js-setup-token-2fa-device');
diff --git a/spec/frontend/authentication/webauthn/authenticate_spec.js b/spec/frontend/authentication/webauthn/authenticate_spec.js
index 8b27560bbbe..b1f4e43e56d 100644
--- a/spec/frontend/authentication/webauthn/authenticate_spec.js
+++ b/spec/frontend/authentication/webauthn/authenticate_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import WebAuthnAuthenticate from '~/authentication/webauthn/authenticate';
import MockWebAuthnDevice from './mock_webauthn_device';
@@ -34,7 +35,7 @@ describe('WebAuthnAuthenticate', () => {
};
beforeEach(() => {
- loadFixtures('webauthn/authenticate.html');
+ loadHTMLFixture('webauthn/authenticate.html');
fallbackElement = document.createElement('div');
fallbackElement.classList.add('js-2fa-form');
webAuthnDevice = new MockWebAuthnDevice();
@@ -62,6 +63,10 @@ describe('WebAuthnAuthenticate', () => {
submitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('with webauthn unavailable', () => {
let oldGetCredentials;
diff --git a/spec/frontend/authentication/webauthn/register_spec.js b/spec/frontend/authentication/webauthn/register_spec.js
index 0f8ea2b635f..95cb993fc70 100644
--- a/spec/frontend/authentication/webauthn/register_spec.js
+++ b/spec/frontend/authentication/webauthn/register_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import setWindowLocation from 'helpers/set_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import WebAuthnRegister from '~/authentication/webauthn/register';
@@ -23,7 +24,7 @@ describe('WebAuthnRegister', () => {
let component;
beforeEach(() => {
- loadFixtures('webauthn/register.html');
+ loadHTMLFixture('webauthn/register.html');
webAuthnDevice = new MockWebAuthnDevice();
container = $('#js-register-token-2fa');
component = new WebAuthnRegister(container, {
@@ -41,6 +42,10 @@ describe('WebAuthnRegister', () => {
component.start();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const findSetupButton = () => container.find('#js-setup-token-2fa-device');
const findMessage = () => container.find('p');
const findDeviceResponse = () => container.find('#js-device-response');
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index c4002ec11f3..b588a27b028 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import loadAwardsHandler from '~/awards_handler';
@@ -75,7 +76,7 @@ describe('AwardsHandler', () => {
beforeEach(async () => {
await initEmojiMock(emojiData);
- loadFixtures('snippets/show.html');
+ loadHTMLFixture('snippets/show.html');
awardsHandler = await loadAwardsHandler(true);
jest.spyOn(awardsHandler, 'postEmoji').mockImplementation((button, url, emoji, cb) => cb());
@@ -91,6 +92,8 @@ describe('AwardsHandler', () => {
$('body').removeAttr('data-page');
awardsHandler.destroy();
+
+ resetHTMLFixture();
});
describe('::showEmojiMenu', () => {
diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js
index ba2ec775b61..6d8a00eb50b 100644
--- a/spec/frontend/badges/components/badge_form_spec.js
+++ b/spec/frontend/badges/components/badge_form_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeForm from '~/badges/components/badge_form.vue';
@@ -16,7 +17,7 @@ describe('BadgeForm component', () => {
let vm;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="dummy-element"></div>
`);
@@ -26,6 +27,7 @@ describe('BadgeForm component', () => {
afterEach(() => {
vm.$destroy();
axiosMock.restore();
+ resetHTMLFixture();
});
describe('methods', () => {
diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js
index 0fb0fa86a02..ad8426f3168 100644
--- a/spec/frontend/badges/components/badge_list_row_spec.js
+++ b/spec/frontend/badges/components/badge_list_row_spec.js
@@ -1,4 +1,5 @@
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@@ -11,7 +12,7 @@ describe('BadgeListRow component', () => {
let vm;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="delete-badge-modal" class="modal"></div>
<div id="dummy-element"></div>
`);
@@ -29,6 +30,7 @@ describe('BadgeListRow component', () => {
afterEach(() => {
vm.$destroy();
+ resetHTMLFixture();
});
it('renders the badge', () => {
diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js
index 39fa502b207..32cd9483ef8 100644
--- a/spec/frontend/badges/components/badge_list_spec.js
+++ b/spec/frontend/badges/components/badge_list_spec.js
@@ -1,4 +1,5 @@
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeList from '~/badges/components/badge_list.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@@ -11,7 +12,7 @@ describe('BadgeList component', () => {
let vm;
beforeEach(() => {
- setFixtures('<div id="dummy-element"></div>');
+ setHTMLFixture('<div id="dummy-element"></div>');
const badges = [];
for (let id = 0; id < numberOfDummyBadges; id += 1) {
badges.push({ id, ...createDummyBadge() });
@@ -34,6 +35,7 @@ describe('BadgeList component', () => {
afterEach(() => {
vm.$destroy();
+ resetHTMLFixture();
});
it('renders a header with the badge count', () => {
diff --git a/spec/frontend/badges/components/badge_spec.js b/spec/frontend/badges/components/badge_spec.js
index fe4cf8ce8eb..19b3a9f23a6 100644
--- a/spec/frontend/badges/components/badge_spec.js
+++ b/spec/frontend/badges/components/badge_spec.js
@@ -1,4 +1,5 @@
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import mountComponent from 'helpers/vue_mount_component_helper';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
import Badge from '~/badges/components/badge.vue';
@@ -90,10 +91,14 @@ describe('Badge component', () => {
describe('behavior', () => {
beforeEach(() => {
- setFixtures('<div id="dummy-element"></div>');
+ setHTMLFixture('<div id="dummy-element"></div>');
return createComponent({ ...dummyProps }, '#dummy-element');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('shows a badge image after loading', () => {
expect(vm.isLoading).toBe(false);
expect(vm.hasError).toBe(false);
diff --git a/spec/frontend/behaviors/autosize_spec.js b/spec/frontend/behaviors/autosize_spec.js
index a9dbee7fd08..7008b7b2eb6 100644
--- a/spec/frontend/behaviors/autosize_spec.js
+++ b/spec/frontend/behaviors/autosize_spec.js
@@ -1,4 +1,5 @@
import '~/behaviors/autosize';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
jest.mock('~/helpers/startup_css_helper', () => {
return {
@@ -20,19 +21,22 @@ jest.mock('~/helpers/startup_css_helper', () => {
describe('Autosize behavior', () => {
beforeEach(() => {
- setFixtures('<textarea class="js-autosize"></textarea>');
+ setHTMLFixture('<textarea class="js-autosize"></textarea>');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('is applied to the textarea', () => {
// This is the second part of the Hack:
// Because we are forcing the mock for WaitForCSSLoaded and the very end of our callstack
// to call its callback. This querySelector needs to go to the very end of our callstack
- // as well, if we would not have this setTimeout Function here, the querySelector
- // would run before the mockImplementation called its callBack Function
- // the DOM Manipulation didn't happen yet and the test would fail.
- setTimeout(() => {
- const textarea = document.querySelector('textarea');
- expect(textarea.classList).toContain('js-autosize-initialized');
- }, 0);
+ // as well, if we would not have this jest.runOnlyPendingTimers here, the querySelector
+ // would not run and the test would fail.
+ jest.runOnlyPendingTimers();
+
+ const textarea = document.querySelector('textarea');
+ expect(textarea.classList).toContain('js-autosize-initialized');
});
});
diff --git a/spec/frontend/behaviors/date_picker_spec.js b/spec/frontend/behaviors/date_picker_spec.js
index 9f7701a0366..363052ad7fb 100644
--- a/spec/frontend/behaviors/date_picker_spec.js
+++ b/spec/frontend/behaviors/date_picker_spec.js
@@ -1,4 +1,5 @@
import * as Pikaday from 'pikaday';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initDatePickers from '~/behaviors/date_picker';
import * as utils from '~/lib/utils/datetime_utility';
@@ -12,7 +13,7 @@ describe('date_picker behavior', () => {
beforeEach(() => {
pikadayMock = jest.spyOn(Pikaday, 'default');
parseMock = jest.spyOn(utils, 'parsePikadayDate');
- setFixtures(`
+ setHTMLFixture(`
<div>
<input class="datepicker" value="2020-10-01" />
</div>
@@ -21,6 +22,10 @@ describe('date_picker behavior', () => {
</div>`);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('Instantiates Pickaday for every instance of a .datepicker class', () => {
initDatePickers();
diff --git a/spec/frontend/behaviors/load_startup_css_spec.js b/spec/frontend/behaviors/load_startup_css_spec.js
index 59f49585645..e9e4c06732f 100644
--- a/spec/frontend/behaviors/load_startup_css_spec.js
+++ b/spec/frontend/behaviors/load_startup_css_spec.js
@@ -1,4 +1,4 @@
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { loadStartupCSS } from '~/behaviors/load_startup_css';
describe('behaviors/load_startup_css', () => {
@@ -25,6 +25,10 @@ describe('behaviors/load_startup_css', () => {
loadStartupCSS();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('does nothing at first', () => {
expect(loadListener).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
index 3305ddc412d..38d19ac3808 100644
--- a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
+++ b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
describe('highlightCurrentUser', () => {
@@ -5,7 +6,7 @@ describe('highlightCurrentUser', () => {
let elements;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="dummy-root-element">
<div data-user="1">@first</div>
<div data-user="2">@second</div>
@@ -15,6 +16,10 @@ describe('highlightCurrentUser', () => {
elements = rootElement.querySelectorAll('[data-user]');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('without current user', () => {
beforeEach(() => {
window.gon = window.gon || {};
diff --git a/spec/frontend/behaviors/markdown/render_sandboxed_mermaid_spec.js b/spec/frontend/behaviors/markdown/render_sandboxed_mermaid_spec.js
index b4844ebc765..2b9442162aa 100644
--- a/spec/frontend/behaviors/markdown/render_sandboxed_mermaid_spec.js
+++ b/spec/frontend/behaviors/markdown/render_sandboxed_mermaid_spec.js
@@ -1,10 +1,11 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import renderMermaid from '~/behaviors/markdown/render_sandboxed_mermaid';
describe('Render mermaid diagrams for Gitlab Flavoured Markdown', () => {
it('Does something', () => {
document.body.dataset.page = '';
- setFixtures(`
+ setHTMLFixture(`
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="1:1-7:3" class="code highlight js-syntax-highlight language-mermaid white" lang="mermaid" id="code-4">
<code class="js-render-mermaid">
@@ -27,5 +28,7 @@ describe('Render mermaid diagrams for Gitlab Flavoured Markdown', () => {
jest.runAllTimers();
expect(document.querySelector('pre.js-syntax-highlight').classList).toContain('gl-sr-only');
+
+ resetHTMLFixture();
});
});
diff --git a/spec/frontend/behaviors/quick_submit_spec.js b/spec/frontend/behaviors/quick_submit_spec.js
index 86a85831c6b..317c671cd2b 100644
--- a/spec/frontend/behaviors/quick_submit_spec.js
+++ b/spec/frontend/behaviors/quick_submit_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import '~/behaviors/quick_submit';
describe('Quick Submit behavior', () => {
@@ -7,7 +8,7 @@ describe('Quick Submit behavior', () => {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
beforeEach(() => {
- loadFixtures('snippets/show.html');
+ loadHTMLFixture('snippets/show.html');
testContext = {};
@@ -24,6 +25,10 @@ describe('Quick Submit behavior', () => {
testContext.textarea = $('.js-quick-submit textarea').first();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('does not respond to other keyCodes', () => {
testContext.textarea.trigger(
keydownEvent({
diff --git a/spec/frontend/behaviors/requires_input_spec.js b/spec/frontend/behaviors/requires_input_spec.js
index bb22133ae44..f2f68f17d1c 100644
--- a/spec/frontend/behaviors/requires_input_spec.js
+++ b/spec/frontend/behaviors/requires_input_spec.js
@@ -1,14 +1,19 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import '~/behaviors/requires_input';
describe('requiresInput', () => {
let submitButton;
beforeEach(() => {
- loadFixtures('branches/new_branch.html');
+ loadHTMLFixture('branches/new_branch.html');
submitButton = $('button[type="submit"]');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('disables submit when any field is required', () => {
$('.js-requires-input').requiresInput();
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index bd5f74247f4..edcacb0f740 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Mousetrap from 'mousetrap';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
@@ -25,7 +26,7 @@ describe('ShortcutsIssuable', () => {
const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
beforeEach(() => {
- loadFixtures(snippetShowFixtureName);
+ loadHTMLFixture(snippetShowFixtureName);
$('body').append(
`<div class="js-main-target-form">
<textarea class="js-vue-comment-form"></textarea>
@@ -40,6 +41,7 @@ describe('ShortcutsIssuable', () => {
$(FORM_SELECTOR).remove();
delete window.shortcut;
+ resetHTMLFixture();
});
// Stub getSelectedFragment to return a node with the provided HTML.
@@ -286,7 +288,7 @@ describe('ShortcutsIssuable', () => {
let sidebarExpandedBtn;
beforeEach(() => {
- loadFixtures(mrShowFixtureName);
+ loadHTMLFixture(mrShowFixtureName);
window.shortcut = new ShortcutsIssuable();
@@ -299,6 +301,7 @@ describe('ShortcutsIssuable', () => {
afterEach(() => {
delete window.shortcut;
+ resetHTMLFixture();
});
describe('when the sidebar is expanded', () => {
diff --git a/spec/frontend/blob/blob_file_dropzone_spec.js b/spec/frontend/blob/blob_file_dropzone_spec.js
index 47c90030e18..d6fc824258b 100644
--- a/spec/frontend/blob/blob_file_dropzone_spec.js
+++ b/spec/frontend/blob/blob_file_dropzone_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', () => {
@@ -6,7 +7,7 @@ describe('BlobFileDropzone', () => {
let replaceFileButton;
beforeEach(() => {
- loadFixtures('blob/show.html');
+ loadHTMLFixture('blob/show.html');
const form = $('.js-upload-blob-form');
// eslint-disable-next-line no-new
new BlobFileDropzone(form, 'POST');
@@ -15,6 +16,10 @@ describe('BlobFileDropzone', () => {
replaceFileButton = $('#submit-all');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('submit button', () => {
it('requires file', () => {
jest.spyOn(window, 'alert').mockImplementation(() => {});
diff --git a/spec/frontend/blob/components/table_contents_spec.js b/spec/frontend/blob/components/table_contents_spec.js
index ade35d39b4f..358ac31819c 100644
--- a/spec/frontend/blob/components/table_contents_spec.js
+++ b/spec/frontend/blob/components/table_contents_spec.js
@@ -1,6 +1,7 @@
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import TableContents from '~/blob/components/table_contents.vue';
let wrapper;
@@ -17,7 +18,7 @@ async function setLoaded(loaded) {
describe('Markdown table of contents component', () => {
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="blob-viewer" data-type="rich" data-loaded="false">
<h1><a href="#1"></a>Hello</h1>
<h2><a href="#2"></a>World</h2>
@@ -29,6 +30,7 @@ describe('Markdown table of contents component', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
describe('not loaded', () => {
diff --git a/spec/frontend/blob/file_template_mediator_spec.js b/spec/frontend/blob/file_template_mediator_spec.js
index 44e12deb564..907a3c97799 100644
--- a/spec/frontend/blob/file_template_mediator_spec.js
+++ b/spec/frontend/blob/file_template_mediator_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import TemplateSelectorMediator from '~/blob/file_template_mediator';
describe('Template Selector Mediator', () => {
@@ -11,7 +12,7 @@ describe('Template Selector Mediator', () => {
}))();
beforeEach(() => {
- setFixtures('<div class="file-editor"><input class="js-file-path-name-input" /></div>');
+ setHTMLFixture('<div class="file-editor"><input class="js-file-path-name-input" /></div>');
input = document.querySelector('.js-file-path-name-input');
mediator = new TemplateSelectorMediator({
editor,
@@ -20,6 +21,10 @@ describe('Template Selector Mediator', () => {
});
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('fills out the input field', () => {
expect(input.value).toBe('');
mediator.setFilename(newFileName);
diff --git a/spec/frontend/blob/line_highlighter_spec.js b/spec/frontend/blob/line_highlighter_spec.js
index 330f1f3137e..21d4e8db503 100644
--- a/spec/frontend/blob/line_highlighter_spec.js
+++ b/spec/frontend/blob/line_highlighter_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable no-return-assign, no-new, no-underscore-dangle */
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import LineHighlighter from '~/blob/line_highlighter';
import * as utils from '~/lib/utils/common_utils';
@@ -14,8 +15,9 @@ describe('LineHighlighter', () => {
const e = $.Event('click', eventData);
return $(`#L${number}`).trigger(e);
};
+
beforeEach(() => {
- loadFixtures('static/line_highlighter.html');
+ loadHTMLFixture('static/line_highlighter.html');
testContext.class = new LineHighlighter();
testContext.css = testContext.class.highlightLineClass;
return (testContext.spies = {
@@ -25,6 +27,10 @@ describe('LineHighlighter', () => {
});
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('behavior', () => {
it('highlights one line given in the URL hash', () => {
new LineHighlighter({ hash: '#L13' });
diff --git a/spec/frontend/blob/openapi/index_spec.js b/spec/frontend/blob/openapi/index_spec.js
index 4b3d3ce2d35..53220809f80 100644
--- a/spec/frontend/blob/openapi/index_spec.js
+++ b/spec/frontend/blob/openapi/index_spec.js
@@ -1,4 +1,5 @@
import { SwaggerUIBundle } from 'swagger-ui-dist';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import renderOpenApi from '~/blob/openapi';
jest.mock('swagger-ui-dist');
@@ -8,10 +9,14 @@ describe('OpenAPI blob viewer', () => {
const mockEndpoint = 'some/endpoint';
beforeEach(() => {
- setFixtures(`<div id="${id}" data-endpoint="${mockEndpoint}"></div>`);
+ setHTMLFixture(`<div id="${id}" data-endpoint="${mockEndpoint}"></div>`);
renderOpenApi();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('initializes SwaggerUI with the correct configuration', () => {
expect(SwaggerUIBundle).toHaveBeenCalledWith({
url: mockEndpoint,
diff --git a/spec/frontend/blob/sketch/index_spec.js b/spec/frontend/blob/sketch/index_spec.js
index d608a6d1f85..5e1922a24f4 100644
--- a/spec/frontend/blob/sketch/index_spec.js
+++ b/spec/frontend/blob/sketch/index_spec.js
@@ -1,4 +1,5 @@
import SketchLoader from '~/blob/sketch';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('jszip', () => {
@@ -15,7 +16,11 @@ jest.mock('jszip', () => {
describe('Sketch viewer', () => {
beforeEach(() => {
- loadFixtures('static/sketch_viewer.html');
+ loadHTMLFixture('static/sketch_viewer.html');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
describe('with error message', () => {
diff --git a/spec/frontend/blob/viewer/index_spec.js b/spec/frontend/blob/viewer/index_spec.js
index fe55a537b89..5f6baf3f63d 100644
--- a/spec/frontend/blob/viewer/index_spec.js
+++ b/spec/frontend/blob/viewer/index_spec.js
@@ -2,6 +2,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import { BlobViewer } from '~/blob/viewer/index';
import axios from '~/lib/utils/axios_utils';
@@ -26,7 +27,7 @@ describe('Blob viewer', () => {
$.fn.extend(jQueryMock);
mock = new MockAdapter(axios);
- loadFixtures('blob/show_readme.html');
+ loadHTMLFixture('blob/show_readme.html');
$('#modal-upload-blob').remove();
mock.onGet(/blob\/.+\/README\.md/).reply(200, {
@@ -39,6 +40,8 @@ describe('Blob viewer', () => {
afterEach(() => {
mock.restore();
window.location.hash = '';
+
+ resetHTMLFixture();
});
it('loads source file after switching views', async () => {
diff --git a/spec/frontend/blob_edit/blob_bundle_spec.js b/spec/frontend/blob_edit/blob_bundle_spec.js
index 2c9ddfaf867..644539308c2 100644
--- a/spec/frontend/blob_edit/blob_bundle_spec.js
+++ b/spec/frontend/blob_edit/blob_bundle_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import blobBundle from '~/blob_edit/blob_bundle';
@@ -14,15 +15,17 @@ describe('BlobBundle', () => {
});
it('loads SourceEditor for the edit screen', async () => {
- setFixtures(`<div class="js-edit-blob-form"></div>`);
+ setHTMLFixture(`<div class="js-edit-blob-form"></div>`);
blobBundle();
await waitForPromises();
expect(SourceEditor).toHaveBeenCalled();
+
+ resetHTMLFixture();
});
describe('No Suggest Popover', () => {
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="js-edit-blob-form" data-blob-filename="blah">
<button class="js-commit-button"></button>
<button id='cancel-changes'></button>
@@ -31,6 +34,10 @@ describe('BlobBundle', () => {
blobBundle();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('sets the window beforeunload listener to a function returning a string', () => {
expect(window.onbeforeunload()).toBe('');
});
@@ -52,7 +59,7 @@ describe('BlobBundle', () => {
let trackingSpy;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="js-edit-blob-form" data-blob-filename="blah" id="target">
<div class="js-suggest-gitlab-ci-yml"
data-target="#target"
@@ -73,6 +80,7 @@ describe('BlobBundle', () => {
afterEach(() => {
unmockTracking();
+ resetHTMLFixture();
});
it('sends a tracking event when the commit button is clicked', () => {
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
index 9c974e79e6e..ae678f6f25f 100644
--- a/spec/frontend/blob_edit/edit_blob_spec.js
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import EditBlob from '~/blob_edit/edit_blob';
import { SourceEditorExtension } from '~/editor/extensions/source_editor_extension_base';
@@ -34,7 +35,7 @@ describe('Blob Editing', () => {
focus: jest.fn(),
};
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<form class="js-edit-blob-form">
<div id="file_path"></div>
<div id="editor"></div>
@@ -48,6 +49,7 @@ describe('Blob Editing', () => {
EditorMarkdownExtension.mockClear();
EditorMarkdownPreviewExtension.mockClear();
FileTemplateExtension.mockClear();
+ resetHTMLFixture();
});
const editorInst = (isMarkdown) => {
diff --git a/spec/frontend/bootstrap_jquery_spec.js b/spec/frontend/bootstrap_jquery_spec.js
index d5d592e3839..15186600a8a 100644
--- a/spec/frontend/bootstrap_jquery_spec.js
+++ b/spec/frontend/bootstrap_jquery_spec.js
@@ -1,10 +1,15 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import '~/commons/bootstrap';
describe('Bootstrap jQuery extensions', () => {
describe('disable', () => {
beforeEach(() => {
- setFixtures('<input type="text" />');
+ setHTMLFixture('<input type="text" />');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('adds the disabled attribute', () => {
@@ -24,7 +29,11 @@ describe('Bootstrap jQuery extensions', () => {
describe('enable', () => {
beforeEach(() => {
- setFixtures('<input type="text" disabled="disabled" class="disabled" />');
+ setHTMLFixture('<input type="text" disabled="disabled" class="disabled" />');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('removes the disabled attribute', () => {
diff --git a/spec/frontend/bootstrap_linked_tabs_spec.js b/spec/frontend/bootstrap_linked_tabs_spec.js
index 30fb140bc69..5ee1ca32141 100644
--- a/spec/frontend/bootstrap_linked_tabs_spec.js
+++ b/spec/frontend/bootstrap_linked_tabs_spec.js
@@ -1,8 +1,13 @@
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('Linked Tabs', () => {
beforeEach(() => {
- loadFixtures('static/linked_tabs.html');
+ loadHTMLFixture('static/linked_tabs.html');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
describe('when is initialized', () => {
diff --git a/spec/frontend/broadcast_notification_spec.js b/spec/frontend/broadcast_notification_spec.js
index cd947cd417a..cfb9fffffc3 100644
--- a/spec/frontend/broadcast_notification_spec.js
+++ b/spec/frontend/broadcast_notification_spec.js
@@ -1,4 +1,5 @@
import Cookies from 'js-cookie';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initBroadcastNotifications from '~/broadcast_notification';
describe('broadcast message on dismiss', () => {
@@ -9,7 +10,7 @@ describe('broadcast message on dismiss', () => {
const endsAt = '2020-01-01T00:00:00Z';
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="js-broadcast-notification-1">
<button class="js-dismiss-current-broadcast-notification" data-id="1" data-expire-date="${endsAt}"></button>
</div>
@@ -18,6 +19,10 @@ describe('broadcast message on dismiss', () => {
initBroadcastNotifications();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('removes broadcast message', () => {
dismiss();
diff --git a/spec/frontend/ci_variable_list/ci_variable_list/ci_variable_list_spec.js b/spec/frontend/ci_variable_list/ci_variable_list/ci_variable_list_spec.js
index 1bca21b1d57..2210b0f48d6 100644
--- a/spec/frontend/ci_variable_list/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/frontend/ci_variable_list/ci_variable_list/ci_variable_list_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import VariableList from '~/ci_variable_list/ci_variable_list';
const HIDE_CLASS = 'hide';
@@ -10,7 +11,7 @@ describe('VariableList', () => {
describe('with only key/value inputs', () => {
describe('with no variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html');
+ loadHTMLFixture('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -20,6 +21,10 @@ describe('VariableList', () => {
variableList.init();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should remove the row when clicking the remove button', () => {
$wrapper.find('.js-row-remove-button').trigger('click');
@@ -64,7 +69,7 @@ describe('VariableList', () => {
describe('with persisted variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html');
+ loadHTMLFixture('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -74,6 +79,10 @@ describe('VariableList', () => {
variableList.init();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should have "Reveal values" button initially when there are already variables', () => {
expect($wrapper.find('.js-secret-value-reveal-button').text()).toBe('Reveal values');
});
@@ -97,7 +106,7 @@ describe('VariableList', () => {
describe('toggleEnableRow method', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html');
+ loadHTMLFixture('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -107,6 +116,10 @@ describe('VariableList', () => {
variableList.init();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should disable all key inputs', () => {
expect($wrapper.find('.js-ci-variable-input-key:not([disabled])').length).toBe(3);
diff --git a/spec/frontend/ci_variable_list/ci_variable_list/native_form_variable_list_spec.js b/spec/frontend/ci_variable_list/ci_variable_list/native_form_variable_list_spec.js
index eee1362440d..57f666e29d6 100644
--- a/spec/frontend/ci_variable_list/ci_variable_list/native_form_variable_list_spec.js
+++ b/spec/frontend/ci_variable_list/ci_variable_list/native_form_variable_list_spec.js
@@ -1,11 +1,12 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
describe('NativeFormVariableList', () => {
let $wrapper;
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html');
+ loadHTMLFixture('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
setupNativeFormVariableList({
@@ -14,6 +15,10 @@ describe('NativeFormVariableList', () => {
});
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('onFormSubmit', () => {
it('should clear out the `name` attribute on the inputs for the last empty row on form submission (avoid BE validation)', () => {
const $row = $wrapper.find('.js-row');
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 2a0610b1b0a..b5345ea8915 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
-import { loadHTMLFixture } from 'helpers/fixtures';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { setTestTimeout } from 'helpers/timeout';
import Clusters from '~/clusters/clusters_bundle';
@@ -27,19 +27,17 @@ describe('Clusters', () => {
beforeEach(() => {
loadHTMLFixture('clusters/show_cluster.html');
- });
- beforeEach(() => {
mockGetClusterStatusRequest();
- });
- beforeEach(() => {
cluster = new Clusters();
});
afterEach(() => {
cluster.destroy();
mock.restore();
+
+ resetHTMLFixture();
});
describe('class constructor', () => {
diff --git a/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js b/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js
index 7d171d0ad35..eeb876a608f 100644
--- a/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js
+++ b/spec/frontend/clusters/gke_cluster_namespace/gke_cluster_namespace_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initGkeNamespace from '~/clusters/gke_cluster_namespace';
describe('GKE cluster namespace', () => {
@@ -10,7 +11,7 @@ describe('GKE cluster namespace', () => {
let glManaged;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<input class="js-gl-managed" type="checkbox" value="1" checked />
<div class="js-namespace">
<input type="text" />
@@ -27,6 +28,10 @@ describe('GKE cluster namespace', () => {
initGkeNamespace();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('GKE cluster namespace toggles', () => {
it('initially displays the GitLab-managed label and input', () => {
expect(isHidden(glManaged)).toEqual(false);
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index f2f97092c5a..b85047dc816 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -1,6 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
+
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import App from '~/code_navigation/components/app.vue';
import Popover from '~/code_navigation/components/popover.vue';
import createState from '~/code_navigation/store/state';
@@ -75,12 +77,14 @@ describe('Code navigation app component', () => {
});
it('calls showDefinition when clicking blob viewer', () => {
- setFixtures('<div class="blob-viewer"></div>');
+ setHTMLFixture('<div class="blob-viewer"></div>');
factory();
document.querySelector('.blob-viewer').click();
expect(showDefinition).toHaveBeenCalled();
+
+ resetHTMLFixture();
});
});
diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js
index c26416aca94..c47a9e697b6 100644
--- a/spec/frontend/code_navigation/store/actions_spec.js
+++ b/spec/frontend/code_navigation/store/actions_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import testAction from 'helpers/vuex_action_helper';
import actions from '~/code_navigation/store/actions';
import { setCurrentHoverElement, addInteractionClass } from '~/code_navigation/utils';
@@ -174,12 +175,16 @@ describe('Code navigation actions', () => {
let target;
beforeEach(() => {
- setFixtures(
+ setHTMLFixture(
'<div data-path="index.js"><div class="line"><div class="js-test"></div></div></div>',
);
target = document.querySelector('.js-test');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('returns early when no data exists', () => {
return testAction(actions.showDefinition, { target }, {}, [], []);
});
diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js
index 682c8bce8c5..b8448709f0b 100644
--- a/spec/frontend/code_navigation/utils/index_spec.js
+++ b/spec/frontend/code_navigation/utils/index_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import {
cachedData,
getCurrentHoverElement,
@@ -35,11 +36,15 @@ describe('setCurrentHoverElement', () => {
describe('addInteractionClass', () => {
beforeEach(() => {
- setFixtures(
+ setHTMLFixture(
'<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"><span>console</span><span>.</span><span>log</span></div><div id="LC2" class="line"><span>function</span></div></div></div>',
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it.each`
line | char | index
${0} | ${0} | ${0}
@@ -59,7 +64,7 @@ describe('addInteractionClass', () => {
describe('wrapTextNodes', () => {
beforeEach(() => {
- setFixtures(
+ setHTMLFixture(
'<div data-path="index.js"><div class="blob-content"><div id="LC1" class="line"> Text </div></div></div>',
);
});
diff --git a/spec/frontend/commits_spec.js b/spec/frontend/commits_spec.js
index a049a6997f0..db1516ed4ec 100644
--- a/spec/frontend/commits_spec.js
+++ b/spec/frontend/commits_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import 'vendor/jquery.endless-scroll';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import CommitsList from '~/commits';
import axios from '~/lib/utils/axios_utils';
import Pager from '~/pager';
@@ -9,7 +10,7 @@ describe('Commits List', () => {
let commitsList;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/main">
<input id="commits-search">
</form>
@@ -19,6 +20,10 @@ describe('Commits List', () => {
commitsList = new CommitsList(25);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should be defined', () => {
expect(CommitsList).toBeDefined();
});
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
index 8f974051232..f660cc8e9de 100644
--- a/spec/frontend/commons/nav/user_merge_requests_spec.js
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import * as UserApi from '~/api/user_api';
import {
openUserCountsBroadcast,
@@ -24,11 +25,15 @@ describe('User Merge Requests', () => {
newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
global.BroadcastChannel = newBroadcastChannelMock;
- setFixtures(
+ setHTMLFixture(
`<div><div class="${MR_COUNT_CLASS}">0</div><div class="js-assigned-mr-count"></div><div class="js-reviewer-mr-count"></div></div>`,
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const findMRCountText = () => document.body.querySelector(`.${MR_COUNT_CLASS}`).textContent;
describe('refreshUserMergeRequestCounts', () => {
diff --git a/spec/frontend/create_item_dropdown_spec.js b/spec/frontend/create_item_dropdown_spec.js
index 143ccb9b930..aea4bc6017d 100644
--- a/spec/frontend/create_item_dropdown_spec.js
+++ b/spec/frontend/create_item_dropdown_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import CreateItemDropdown from '~/create_item_dropdown';
const DROPDOWN_ITEM_DATA = [
@@ -41,12 +42,13 @@ describe('CreateItemDropdown', () => {
}
beforeEach(() => {
- loadFixtures('static/create_item_dropdown.html');
+ loadHTMLFixture('static/create_item_dropdown.html');
$wrapperEl = $('.js-create-item-dropdown-fixture-root');
});
afterEach(() => {
$wrapperEl.remove();
+ resetHTMLFixture();
});
describe('items', () => {
diff --git a/spec/frontend/deprecated_jquery_dropdown_spec.js b/spec/frontend/deprecated_jquery_dropdown_spec.js
index bec91fe5fc5..b18d53b317d 100644
--- a/spec/frontend/deprecated_jquery_dropdown_spec.js
+++ b/spec/frontend/deprecated_jquery_dropdown_spec.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import mockProjects from 'test_fixtures_static/projects.json';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import '~/lib/utils/common_utils';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -64,7 +65,7 @@ describe('deprecatedJQueryDropdown', () => {
}
beforeEach(() => {
- loadFixtures('static/deprecated_jquery_dropdown.html');
+ loadHTMLFixture('static/deprecated_jquery_dropdown.html');
test.dropdownContainerElement = $('.dropdown.inline');
test.$dropdownMenuElement = $('.dropdown-menu', test.dropdownContainerElement);
test.projectsData = JSON.parse(JSON.stringify(mockProjects));
@@ -73,6 +74,8 @@ describe('deprecatedJQueryDropdown', () => {
afterEach(() => {
$('body').off('keydown');
test.dropdownContainerElement.off('keyup');
+
+ resetHTMLFixture();
});
it('should open on click', () => {
diff --git a/spec/frontend/dropzone_input_spec.js b/spec/frontend/dropzone_input_spec.js
index 11414e8890d..a633de9ef56 100644
--- a/spec/frontend/dropzone_input_spec.js
+++ b/spec/frontend/dropzone_input_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import mock from 'xhr-mock';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import PasteMarkdownTable from '~/behaviors/markdown/paste_markdown_table';
@@ -45,7 +46,7 @@ describe('dropzone_input', () => {
};
beforeEach(() => {
- loadFixtures('issues/new-issue.html');
+ loadHTMLFixture('issues/new-issue.html');
form = $('#new_issue');
form.data('uploads-path', TEST_UPLOAD_PATH);
@@ -54,6 +55,8 @@ describe('dropzone_input', () => {
afterEach(() => {
form = null;
+
+ resetHTMLFixture();
});
it('pastes Markdown tables', () => {
diff --git a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
index 2f6d277ca75..9a14e1a55eb 100644
--- a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
+++ b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
@@ -1,4 +1,5 @@
import { languages } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import { CiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext';
import ciSchemaPath from '~/editor/schema/ci.json';
@@ -19,7 +20,7 @@ describe('~/editor/editor_ci_config_ext', () => {
let originalGitlabUrl;
const createMockEditor = ({ blobPath = defaultBlobPath } = {}) => {
- setFixtures('<div id="editor"></div>');
+ setHTMLFixture('<div id="editor"></div>');
editorEl = document.getElementById('editor');
editor = new SourceEditor();
instance = editor.createInstance({
@@ -45,7 +46,9 @@ describe('~/editor/editor_ci_config_ext', () => {
afterEach(() => {
instance.dispose();
+
editorEl.remove();
+ resetHTMLFixture();
});
describe('registerCiSchema', () => {
diff --git a/spec/frontend/editor/source_editor_extension_base_spec.js b/spec/frontend/editor/source_editor_extension_base_spec.js
index 6606557fd1f..eab39ccaba1 100644
--- a/spec/frontend/editor/source_editor_extension_base_spec.js
+++ b/spec/frontend/editor/source_editor_extension_base_spec.js
@@ -1,4 +1,5 @@
import { Range } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import setWindowLocation from 'helpers/set_window_location_helper';
import {
@@ -39,12 +40,13 @@ describe('The basis for an Source Editor extension', () => {
};
beforeEach(() => {
- setFixtures(generateLines());
+ setHTMLFixture(generateLines());
event = generateEventMock();
});
afterEach(() => {
jest.clearAllMocks();
+ resetHTMLFixture();
});
describe('onUse callback', () => {
@@ -253,7 +255,7 @@ describe('The basis for an Source Editor extension', () => {
});
it('does not create a link if the event is triggered on a wrong node', () => {
- setFixtures('<div class="wrong-class">3</div>');
+ setHTMLFixture('<div class="wrong-class">3</div>');
SourceEditorExtension.createAnchor = jest.fn();
const wrongEvent = generateEventMock({ el: document.querySelector('.wrong-class') });
diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js
index eecd23bff6e..3e8c287df2f 100644
--- a/spec/frontend/editor/source_editor_markdown_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { Range, Position } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
import SourceEditor from '~/editor/source_editor';
import axios from '~/lib/utils/axios_utils';
@@ -27,7 +28,7 @@ describe('Markdown Extension for Source Editor', () => {
beforeEach(() => {
mockAxios = new MockAdapter(axios);
- setFixtures('<div id="editor" data-editor-loading></div>');
+ setHTMLFixture('<div id="editor" data-editor-loading></div>');
editorEl = document.getElementById('editor');
editor = new SourceEditor();
instance = editor.createInstance({
@@ -42,6 +43,8 @@ describe('Markdown Extension for Source Editor', () => {
instance.dispose();
editorEl.remove();
mockAxios.restore();
+
+ resetHTMLFixture();
});
describe('getSelectedText', () => {
diff --git a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
index c8d016e10ac..f239dbcd665 100644
--- a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import { editor as monacoEditor } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import {
EXTENSION_MARKDOWN_PREVIEW_PANEL_CLASS,
@@ -41,7 +42,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
beforeEach(() => {
mockAxios = new MockAdapter(axios);
- setFixtures('<div id="editor" data-editor-loading></div>');
+ setHTMLFixture('<div id="editor" data-editor-loading></div>');
editorEl = document.getElementById('editor');
editor = new SourceEditor();
instance = editor.createInstance({
@@ -60,6 +61,7 @@ describe('Markdown Live Preview Extension for Source Editor', () => {
instance.dispose();
editorEl.remove();
mockAxios.restore();
+ resetHTMLFixture();
});
it('sets up the preview on the instance', () => {
diff --git a/spec/frontend/editor/source_editor_spec.js b/spec/frontend/editor/source_editor_spec.js
index 049cab3a83b..b3d914e6755 100644
--- a/spec/frontend/editor/source_editor_spec.js
+++ b/spec/frontend/editor/source_editor_spec.js
@@ -1,4 +1,5 @@
import { editor as monacoEditor, languages as monacoLanguages } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import {
SOURCE_EDITOR_INSTANCE_ERROR_NO_EL,
URI_PREFIX,
@@ -33,7 +34,7 @@ describe('Base editor', () => {
const blobGlobalId = 'snippet_777';
beforeEach(() => {
- setFixtures('<div id="editor" data-editor-loading></div>');
+ setHTMLFixture('<div id="editor" data-editor-loading></div>');
editorEl = document.getElementById('editor');
defaultArguments = { el: editorEl, blobPath, blobContent, blobGlobalId };
editor = new SourceEditor();
@@ -45,6 +46,8 @@ describe('Base editor', () => {
monacoEditor.getModels().forEach((model) => {
model.dispose();
});
+
+ resetHTMLFixture();
});
const uriFilePath = joinPaths('/', URI_PREFIX, blobGlobalId, blobPath);
@@ -244,7 +247,7 @@ describe('Base editor', () => {
const readOnlyIndex = '78'; // readOnly option has the internal index of 78 in the editor's options
beforeEach(() => {
- setFixtures('<div id="editor1"></div><div id="editor2"></div>');
+ setHTMLFixture('<div id="editor1"></div><div id="editor2"></div>');
editorEl1 = document.getElementById('editor1');
editorEl2 = document.getElementById('editor2');
inst1Args = {
@@ -262,6 +265,7 @@ describe('Base editor', () => {
afterEach(() => {
editor.dispose();
+ resetHTMLFixture();
});
it('can initialize several instances of the same editor', () => {
diff --git a/spec/frontend/editor/source_editor_yaml_ext_spec.js b/spec/frontend/editor/source_editor_yaml_ext_spec.js
index b603b0e3a98..14ec7f8b93f 100644
--- a/spec/frontend/editor/source_editor_yaml_ext_spec.js
+++ b/spec/frontend/editor/source_editor_yaml_ext_spec.js
@@ -1,4 +1,5 @@
import { Document } from 'yaml';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import SourceEditor from '~/editor/source_editor';
import { YamlEditorExtension } from '~/editor/extensions/source_editor_yaml_ext';
import { SourceEditorExtension } from '~/editor/extensions/source_editor_extension_base';
@@ -8,7 +9,7 @@ let baseExtension;
let yamlExtension;
const getEditorInstance = (editorInstanceOptions = {}) => {
- setFixtures('<div id="editor"></div>');
+ setHTMLFixture('<div id="editor"></div>');
return new SourceEditor().createInstance({
el: document.getElementById('editor'),
blobPath: '.gitlab-ci.yml',
@@ -18,7 +19,7 @@ const getEditorInstance = (editorInstanceOptions = {}) => {
};
const getEditorInstanceWithExtension = (extensionOptions = {}, editorInstanceOptions = {}) => {
- setFixtures('<div id="editor"></div>');
+ setHTMLFixture('<div id="editor"></div>');
const instance = getEditorInstance(editorInstanceOptions);
[baseExtension, yamlExtension] = instance.use([
{ definition: SourceEditorExtension },
@@ -35,6 +36,10 @@ const getEditorInstanceWithExtension = (extensionOptions = {}, editorInstanceOpt
};
describe('YamlCreatorExtension', () => {
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('constructor', () => {
it('saves setupOptions options on the extension, but does not expose those to instance', () => {
const highlightPath = 'foo';
diff --git a/spec/frontend/editor/utils_spec.js b/spec/frontend/editor/utils_spec.js
index 97d3e9e081d..e561cad1086 100644
--- a/spec/frontend/editor/utils_spec.js
+++ b/spec/frontend/editor/utils_spec.js
@@ -1,4 +1,5 @@
import { editor as monacoEditor } from 'monaco-editor';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import * as utils from '~/editor/utils';
import { DEFAULT_THEME } from '~/ide/lib/themes';
@@ -14,10 +15,14 @@ describe('Source Editor utils', () => {
describe('clearDomElement', () => {
beforeEach(() => {
- setFixtures('<div id="foo"><div id="bar">Foo</div></div>');
+ setHTMLFixture('<div id="foo"><div id="bar">Foo</div></div>');
el = document.getElementById('foo');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('removes all child nodes from an element', () => {
expect(el.children.length).toBe(1);
utils.clearDomElement(el);
@@ -68,10 +73,14 @@ describe('Source Editor utils', () => {
beforeEach(() => {
jest.spyOn(monacoEditor, 'colorizeElement').mockImplementation();
jest.spyOn(monacoEditor, 'setTheme').mockImplementation();
- setFixtures('<pre id="foo"></pre>');
+ setHTMLFixture('<pre id="foo"></pre>');
el = document.getElementById('foo');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('colorizes the element and applies the preference theme', () => {
expect(monacoEditor.colorizeElement).not.toHaveBeenCalled();
expect(monacoEditor.setTheme).not.toHaveBeenCalled();
diff --git a/spec/frontend/filterable_list_spec.js b/spec/frontend/filterable_list_spec.js
index 3fd5d198e3a..e7197ac6dbf 100644
--- a/spec/frontend/filterable_list_spec.js
+++ b/spec/frontend/filterable_list_spec.js
@@ -1,4 +1,4 @@
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilterableList from '~/filterable_list';
describe('FilterableList', () => {
@@ -20,6 +20,10 @@ describe('FilterableList', () => {
List = new FilterableList(form, filter, holder);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('processes input parameters', () => {
expect(List.filterForm).toEqual(form);
expect(List.listFilterElement).toEqual(filter);
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index ee0eef6a1b6..26f12673f68 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -1,3 +1,4 @@
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import DropdownUser from '~/filtered_search/dropdown_user';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer';
@@ -80,7 +81,7 @@ describe('Dropdown User', () => {
let authorFilterDropdownElement;
beforeEach(() => {
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
authorFilterDropdownElement = document.querySelector('#js-dropdown-author');
const dummyInput = document.createElement('div');
dropdown = new DropdownUser({
@@ -89,6 +90,10 @@ describe('Dropdown User', () => {
});
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const findCurrentUserElement = () =>
authorFilterDropdownElement.querySelector('.js-current-user');
diff --git a/spec/frontend/filtered_search/dropdown_utils_spec.js b/spec/frontend/filtered_search/dropdown_utils_spec.js
index 4c1e79eba42..2030b45b44c 100644
--- a/spec/frontend/filtered_search/dropdown_utils_spec.js
+++ b/spec/frontend/filtered_search/dropdown_utils_spec.js
@@ -1,3 +1,4 @@
+import { loadHTMLFixture, setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
@@ -43,13 +44,17 @@ describe('Dropdown Utils', () => {
};
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<input type="text" id="test" />
`);
input = document.getElementById('test');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should filter without symbol', () => {
input.value = 'roo';
@@ -142,7 +147,7 @@ describe('Dropdown Utils', () => {
let allowedKeys;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search" type="text" id="test" />
@@ -350,7 +355,7 @@ describe('Dropdown Utils', () => {
let authorToken;
beforeEach(() => {
- loadFixtures(issuableListFixture);
+ loadHTMLFixture(issuableListFixture);
authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user');
const searchTermToken = FilteredSearchSpecHelper.createSearchVisualToken('search term');
diff --git a/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js b/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js
index e9ee69ca163..dff6d11a320 100644
--- a/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_dropdown_manager_spec.js
@@ -1,5 +1,6 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
describe('Filtered Search Dropdown Manager', () => {
@@ -20,7 +21,7 @@ describe('Filtered Search Dropdown Manager', () => {
}
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<ul class="tokens-container">
<li class="input-token">
<input class="filtered-search">
@@ -29,6 +30,10 @@ describe('Filtered Search Dropdown Manager', () => {
`);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('input has no existing value', () => {
it('should add just tokenName', () => {
FilteredSearchDropdownManager.addWordToInput({ tokenName: 'milestone' });
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index 911a507af4c..5e68725c03e 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -1,5 +1,5 @@
import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
-
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
@@ -64,7 +64,7 @@ describe('Filtered Search Manager', () => {
}
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="filtered-search-box">
<form>
<ul class="tokens-container list-unstyled">
@@ -80,6 +80,10 @@ describe('Filtered Search Manager', () => {
jest.spyOn(FilteredSearchDropdownManager.prototype, 'setDropdown').mockImplementation();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const initializeManager = ({ useDefaultState } = {}) => {
jest.spyOn(FilteredSearchManager.prototype, 'loadSearchParamsFromURL').mockImplementation();
jest.spyOn(FilteredSearchManager.prototype, 'tokenChange').mockImplementation();
diff --git a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
index c4e125e96da..0e5c94edd05 100644
--- a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,5 +1,6 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import waitForPromises from 'helpers/wait_for_promises';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
@@ -24,7 +25,7 @@ describe('Filtered Search Visual Tokens', () => {
mock = new MockAdapter(axios);
mock.onGet().reply(200);
- setFixtures(`
+ setHTMLFixture(`
<ul class="tokens-container">
${FilteredSearchSpecHelper.createInputHTML()}
</ul>
@@ -35,6 +36,10 @@ describe('Filtered Search Visual Tokens', () => {
bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('getLastVisualTokenBeforeInput', () => {
it('returns when there are no visual tokens', () => {
const { lastVisualToken, isLastVisualTokenValid } = subject.getLastVisualTokenBeforeInput();
@@ -241,7 +246,7 @@ describe('Filtered Search Visual Tokens', () => {
let tokenElement;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="test-area">
${subject.createVisualTokenElementHTML('custom-token')}
</div>
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index bf526a8d371..e52ffa7bd9f 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -1,5 +1,6 @@
import { escape } from 'lodash';
import labelData from 'test_fixtures/labels/project_labels.json';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import { TEST_HOST } from 'helpers/test_constants';
import DropdownUtils from '~/filtered_search/dropdown_utils';
@@ -28,7 +29,7 @@ describe('Filtered Search Visual Tokens', () => {
let bugLabelToken;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<ul class="tokens-container">
${FilteredSearchSpecHelper.createInputHTML()}
</ul>
@@ -39,6 +40,10 @@ describe('Filtered Search Visual Tokens', () => {
bugLabelToken = FilteredSearchSpecHelper.createFilterVisualToken('label', '=', '~bug');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('updateUserTokenAppearance', () => {
let usersCacheSpy;
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js
index 942e2c330fa..6cd32ff6b40 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/flash_spec.js
@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/browser';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import createFlash, {
hideFlash,
addDismissFlashClickListener,
@@ -93,7 +93,7 @@ describe('Flash', () => {
if (alert) {
alert.$destroy();
}
- document.querySelector('.flash-container')?.remove();
+ resetHTMLFixture();
});
it('adds alert element into the document by default', () => {
@@ -330,7 +330,7 @@ describe('Flash', () => {
});
afterEach(() => {
- document.querySelector('.js-content-wrapper').remove();
+ resetHTMLFixture();
});
it('adds flash alert element into the document by default', () => {
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index 1ab3286fe4c..aa98b2774ea 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -2,6 +2,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import labelsFixture from 'test_fixtures/autocomplete_sources/labels.json';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GfmAutoComplete, { membersBeforeSave, highlighter } from 'ee_else_ce/gfm_auto_complete';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import '~/lib/utils/jquery_at_who';
@@ -722,7 +723,7 @@ describe('GfmAutoComplete', () => {
let $textarea;
beforeEach(() => {
- setFixtures('<textarea></textarea>');
+ setHTMLFixture('<textarea></textarea>');
autocomplete = new GfmAutoComplete(dataSources);
$textarea = $('textarea');
autocomplete.setup($textarea, { labels: true });
@@ -730,6 +731,7 @@ describe('GfmAutoComplete', () => {
afterEach(() => {
autocomplete.destroy();
+ resetHTMLFixture();
});
const triggerDropdown = (text) => {
diff --git a/spec/frontend/gl_field_errors_spec.js b/spec/frontend/gl_field_errors_spec.js
index ada3b34e6b1..92d04927ee5 100644
--- a/spec/frontend/gl_field_errors_spec.js
+++ b/spec/frontend/gl_field_errors_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GlFieldErrors from '~/gl_field_errors';
describe('GL Style Field Errors', () => {
@@ -9,13 +10,17 @@ describe('GL Style Field Errors', () => {
});
beforeEach(() => {
- loadFixtures('static/gl_field_errors.html');
+ loadHTMLFixture('static/gl_field_errors.html');
const $form = $('form.gl-show-field-errors');
testContext.$form = $form;
testContext.fieldErrors = new GlFieldErrors($form);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should select the correct input elements', () => {
expect(testContext.$form).toBeDefined();
expect(testContext.$form.length).toBe(1);
diff --git a/spec/frontend/google_tag_manager/index_spec.js b/spec/frontend/google_tag_manager/index_spec.js
index de4a57a7319..6412fe8bb33 100644
--- a/spec/frontend/google_tag_manager/index_spec.js
+++ b/spec/frontend/google_tag_manager/index_spec.js
@@ -14,7 +14,7 @@ import {
trackTransaction,
trackAddToCartUsageTab,
} from '~/google_tag_manager';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { logError } from '~/lib/logger';
jest.mock('~/lib/logger');
@@ -216,6 +216,10 @@ describe('~/google_tag_manager/index', () => {
subject();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it.each(expectedEvents)('when %p', ({ selector, trigger, expectation }) => {
expect(spy).not.toHaveBeenCalled();
@@ -443,6 +447,8 @@ describe('~/google_tag_manager/index', () => {
expect(spy).not.toHaveBeenCalled();
expect(logError).not.toHaveBeenCalled();
+
+ resetHTMLFixture();
});
});
@@ -468,6 +474,8 @@ describe('~/google_tag_manager/index', () => {
'Unexpected error while pushing to dataLayer',
pushError,
);
+
+ resetHTMLFixture();
});
});
});
diff --git a/spec/frontend/gpg_badges_spec.js b/spec/frontend/gpg_badges_spec.js
index 0bb50fc3e6f..0a1596b492d 100644
--- a/spec/frontend/gpg_badges_spec.js
+++ b/spec/frontend/gpg_badges_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'spec/test_constants';
import GpgBadges from '~/gpg_badges';
import axios from '~/lib/utils/axios_utils';
@@ -18,7 +19,7 @@ describe('GpgBadges', () => {
const dummyUrl = `${TEST_HOST}/dummy/signatures`;
const setForm = ({ utf8 = '✓', search = '' } = {}) => {
- setFixtures(`
+ setHTMLFixture(`
<form
class="commits-search-form js-signature-container" data-signatures-path="${dummyUrl}" action="${dummyUrl}"
method="get">
@@ -38,24 +39,27 @@ describe('GpgBadges', () => {
afterEach(() => {
mock.restore();
+ resetHTMLFixture();
});
it('does not make a request if there is no container element', async () => {
- setFixtures('');
+ setHTMLFixture('');
jest.spyOn(axios, 'get').mockImplementation(() => {});
await GpgBadges.fetch();
expect(axios.get).not.toHaveBeenCalled();
+ resetHTMLFixture();
});
it('throws an error if the endpoint is missing', async () => {
- setFixtures('<div class="js-signature-container"></div>');
+ setHTMLFixture('<div class="js-signature-container"></div>');
jest.spyOn(axios, 'get').mockImplementation(() => {});
await expect(GpgBadges.fetch()).rejects.toEqual(
new Error('Missing commit signatures endpoint!'),
);
expect(axios.get).not.toHaveBeenCalled();
+ resetHTMLFixture();
});
it('fetches commit signatures', async () => {
diff --git a/spec/frontend/header_spec.js b/spec/frontend/header_spec.js
index 937bc9aa478..19849fba63c 100644
--- a/spec/frontend/header_spec.js
+++ b/spec/frontend/header_spec.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import initTodoToggle, { initNavUserDropdownTracking } from '~/header';
+import { loadHTMLFixture, setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
describe('Header', () => {
describe('Todos notification', () => {
@@ -17,7 +18,11 @@ describe('Header', () => {
beforeEach(() => {
initTodoToggle();
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('should update todos-count after receiving the todo:toggle event', () => {
@@ -57,7 +62,7 @@ describe('Header', () => {
let trackingSpy;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<li class="js-nav-user-dropdown">
<a class="js-buy-pipeline-minutes-link" data-track-action="click_buy_ci_minutes" data-track-label="free" data-track-property="user_dropdown">Buy Pipeline minutes</a>
</li>`);
@@ -70,6 +75,7 @@ describe('Header', () => {
afterEach(() => {
unmockTracking();
+ resetHTMLFixture();
});
it('sends a tracking event when the dropdown is opened and contains Buy Pipeline minutes link', () => {
diff --git a/spec/frontend/helpers/startup_css_helper_spec.js b/spec/frontend/helpers/startup_css_helper_spec.js
index 703bdbd342f..2236b5aa261 100644
--- a/spec/frontend/helpers/startup_css_helper_spec.js
+++ b/spec/frontend/helpers/startup_css_helper_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { waitForCSSLoaded } from '~/helpers/startup_css_helper';
describe('waitForCSSLoaded', () => {
@@ -41,17 +42,19 @@ describe('waitForCSSLoaded', () => {
describe('with startup css enabled', () => {
it('should dispatch CSSLoaded when the assets are cached or already loaded', async () => {
- setFixtures(`
+ setHTMLFixture(`
<link href="one.css" data-startupcss="loaded">
<link href="two.css" data-startupcss="loaded">
`);
await waitForCSSLoaded(mockedCallback);
expect(mockedCallback).toHaveBeenCalledTimes(1);
+
+ resetHTMLFixture();
});
it('should wait to call CssLoaded until the assets are loaded', async () => {
- setFixtures(`
+ setHTMLFixture(`
<link href="one.css" data-startupcss="loading">
<link href="two.css" data-startupcss="loading">
`);
@@ -63,6 +66,8 @@ describe('waitForCSSLoaded', () => {
await events;
expect(mockedCallback).toHaveBeenCalledTimes(1);
+
+ resetHTMLFixture();
});
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
index e66de6bb0b0..ace266aec5e 100644
--- a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
@@ -1,4 +1,5 @@
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import createComponent from 'helpers/vue_mount_component_helper';
import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue';
@@ -7,7 +8,7 @@ describe('IDE commit message field', () => {
let vm;
beforeEach(() => {
- setFixtures('<div id="app"></div>');
+ setHTMLFixture('<div id="app"></div>');
vm = createComponent(
Component,
@@ -21,6 +22,8 @@ describe('IDE commit message field', () => {
afterEach(() => {
vm.$destroy();
+
+ resetHTMLFixture();
});
it('adds is-focused class on focus', async () => {
diff --git a/spec/frontend/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index 710aa7108a8..f8faa8d78c2 100644
--- a/spec/frontend/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import imageDiffHelper from '~/image_diff/helpers/index';
import ImageDiff from '~/image_diff/image_diff';
@@ -9,7 +10,7 @@ describe('ImageDiff', () => {
let imageDiff;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="element">
<div class="diff-file">
<div class="js-image-frame">
@@ -35,6 +36,10 @@ describe('ImageDiff', () => {
element = document.getElementById('element');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('constructor', () => {
beforeEach(() => {
imageDiff = new ImageDiff(element, {
diff --git a/spec/frontend/image_diff/init_discussion_tab_spec.js b/spec/frontend/image_diff/init_discussion_tab_spec.js
index f6f05037c95..3b427f0d54d 100644
--- a/spec/frontend/image_diff/init_discussion_tab_spec.js
+++ b/spec/frontend/image_diff/init_discussion_tab_spec.js
@@ -1,9 +1,10 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initImageDiffHelper from '~/image_diff/helpers/init_image_diff';
import initDiscussionTab from '~/image_diff/init_discussion_tab';
describe('initDiscussionTab', () => {
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="timeline-content">
<div class="diff-file js-image-file"></div>
<div class="diff-file js-image-file"></div>
@@ -11,6 +12,10 @@ describe('initDiscussionTab', () => {
`);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should pass canCreateNote as false to initImageDiff', () => {
jest
.spyOn(initImageDiffHelper, 'initImageDiff')
diff --git a/spec/frontend/image_diff/replaced_image_diff_spec.js b/spec/frontend/image_diff/replaced_image_diff_spec.js
index 2b401fc46bf..d789e964e4c 100644
--- a/spec/frontend/image_diff/replaced_image_diff_spec.js
+++ b/spec/frontend/image_diff/replaced_image_diff_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import imageDiffHelper from '~/image_diff/helpers/index';
import ImageDiff from '~/image_diff/image_diff';
@@ -9,7 +10,7 @@ describe('ReplacedImageDiff', () => {
let replacedImageDiff;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="element">
<div class="two-up">
<div class="js-image-frame">
@@ -36,6 +37,10 @@ describe('ReplacedImageDiff', () => {
element = document.getElementById('element');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
function setupImageFrameEls() {
replacedImageDiff.imageFrameEls = [];
replacedImageDiff.imageFrameEls[viewTypes.TWO_UP] = element.querySelector(
diff --git a/spec/frontend/issuable/issuable_form_spec.js b/spec/frontend/issuable/issuable_form_spec.js
index 99ed18cf5bd..a1583076b41 100644
--- a/spec/frontend/issuable/issuable_form_spec.js
+++ b/spec/frontend/issuable/issuable_form_spec.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import IssuableForm from '~/issuable/issuable_form';
import setWindowLocation from 'helpers/set_window_location_helper';
@@ -11,7 +11,7 @@ describe('IssuableForm', () => {
};
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<form>
<input name="[title]" />
</form>
@@ -19,6 +19,10 @@ describe('IssuableForm', () => {
createIssuable($('form'));
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('initAutosave', () => {
it('creates autosave with the searchTerm included', () => {
setWindowLocation('https://gitlab.test/foo?bar=true');
@@ -28,7 +32,7 @@ describe('IssuableForm', () => {
});
it("creates autosave fields without the searchTerm if it's an issue new form", () => {
- setFixtures(`
+ setHTMLFixture(`
<form data-new-issue-path="/issues/new">
<input name="[title]" />
</form>
diff --git a/spec/frontend/issues/issue_spec.js b/spec/frontend/issues/issue_spec.js
index b4f6118ec20..089ea8dbbad 100644
--- a/spec/frontend/issues/issue_spec.js
+++ b/spec/frontend/issues/issue_spec.js
@@ -1,5 +1,6 @@
import { getByText } from '@testing-library/dom';
import MockAdapter from 'axios-mock-adapter';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import Issue from '~/issues/issue';
import axios from '~/lib/utils/axios_utils';
@@ -38,9 +39,9 @@ describe('Issue', () => {
`('$desc', ({ isIssueInitiallyOpen, expectedCounterText }) => {
beforeEach(() => {
if (isIssueInitiallyOpen) {
- loadFixtures('issues/open-issue.html');
+ loadHTMLFixture('issues/open-issue.html');
} else {
- loadFixtures('issues/closed-issue.html');
+ loadHTMLFixture('issues/closed-issue.html');
}
testContext.issueCounter = getIssueCounter();
@@ -50,6 +51,10 @@ describe('Issue', () => {
testContext.issueCounter.textContent = '1,001';
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it(`has the proper visible status box when ${isIssueInitiallyOpen ? 'open' : 'closed'}`, () => {
if (isIssueInitiallyOpen) {
expect(testContext.statusBoxClosed).toHaveClass('hidden');
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 8a6dadee056..54ac7b21d0e 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -1,6 +1,7 @@
import { GlIntersectionObserver } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import '~/behaviors/markdown/render_gfm';
@@ -70,7 +71,7 @@ describe('Issuable output', () => {
};
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div>
<title>Title</title>
<div class="detail-page-description content-block">
@@ -105,6 +106,7 @@ describe('Issuable output', () => {
realtimeRequestCount = 0;
wrapper.vm.poll.stop();
wrapper.destroy();
+ resetHTMLFixture();
});
it('should render a title/description/edited and update title/description/edited on update', () => {
diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js
index 29b5353ef1c..7560b733ae6 100644
--- a/spec/frontend/issues/show/components/title_spec.js
+++ b/spec/frontend/issues/show/components/title_spec.js
@@ -1,4 +1,5 @@
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import titleComponent from '~/issues/show/components/title.vue';
import eventHub from '~/issues/show/event_hub';
import Store from '~/issues/show/stores';
@@ -6,7 +7,7 @@ import Store from '~/issues/show/stores';
describe('Title component', () => {
let vm;
beforeEach(() => {
- setFixtures(`<title />`);
+ setHTMLFixture(`<title />`);
const Component = Vue.extend(titleComponent);
const store = new Store({
@@ -25,6 +26,10 @@ describe('Title component', () => {
}).$mount();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('renders title HTML', () => {
expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
});
diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js
index 766772cb521..88dac449527 100644
--- a/spec/frontend/lib/utils/dom_utils_spec.js
+++ b/spec/frontend/lib/utils/dom_utils_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import {
addClassIfElementExists,
canScrollUp,
@@ -24,10 +25,14 @@ describe('DOM Utils', () => {
let parentElement;
beforeEach(() => {
- setFixtures(fixture);
+ setHTMLFixture(fixture);
parentElement = document.querySelector('.parent');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('adds class if element exists', () => {
const childElement = parentElement.querySelector('.child');
@@ -127,10 +132,14 @@ describe('DOM Utils', () => {
let element;
beforeEach(() => {
- setFixtures('<div data-foo-bar data-baz data-qux="">');
+ setHTMLFixture('<div data-foo-bar data-baz data-qux="">');
element = document.querySelector('[data-foo-bar]');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('throws if not given an element', () => {
expect(() => parseBooleanDataAttributes(null, ['baz'])).toThrow();
});
diff --git a/spec/frontend/lib/utils/file_upload_spec.js b/spec/frontend/lib/utils/file_upload_spec.js
index ff11107ea60..f63af2fe0a4 100644
--- a/spec/frontend/lib/utils/file_upload_spec.js
+++ b/spec/frontend/lib/utils/file_upload_spec.js
@@ -1,8 +1,9 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import fileUpload, { getFilename, validateImageName } from '~/lib/utils/file_upload';
describe('File upload', () => {
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<form>
<button class="js-button" type="button">Click me!</button>
<input type="text" class="js-input" />
@@ -11,6 +12,10 @@ describe('File upload', () => {
`);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('when there is a matching button and input', () => {
beforeEach(() => {
fileUpload('.js-button', '.js-input');
diff --git a/spec/frontend/lib/utils/navigation_utility_spec.js b/spec/frontend/lib/utils/navigation_utility_spec.js
index 6a880a0f354..632a8904578 100644
--- a/spec/frontend/lib/utils/navigation_utility_spec.js
+++ b/spec/frontend/lib/utils/navigation_utility_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import findAndFollowLink from '~/lib/utils/navigation_utility';
import * as navigationUtils from '~/lib/utils/navigation_utility';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -8,11 +9,13 @@ describe('findAndFollowLink', () => {
it('visits a link when the selector exists', () => {
const href = '/some/path';
- setFixtures(`<a class="my-shortcut" href="${href}">link</a>`);
+ setHTMLFixture(`<a class="my-shortcut" href="${href}">link</a>`);
findAndFollowLink('.my-shortcut');
expect(visitUrl).toHaveBeenCalledWith(href);
+
+ resetHTMLFixture();
});
it('does not throw an exception when the selector does not exist', () => {
diff --git a/spec/frontend/lib/utils/resize_observer_spec.js b/spec/frontend/lib/utils/resize_observer_spec.js
index 6560562f204..c88ba73ebc6 100644
--- a/spec/frontend/lib/utils/resize_observer_spec.js
+++ b/spec/frontend/lib/utils/resize_observer_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { contentTop } from '~/lib/utils/common_utils';
import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
@@ -19,7 +20,7 @@ describe('ResizeObserver Utility', () => {
jest.spyOn(document.documentElement, 'scrollTo');
- setFixtures(`<div id="content-body"><div id="note_1234">note to scroll to</div></div>`);
+ setHTMLFixture(`<div id="content-body"><div id="note_1234">note to scroll to</div></div>`);
const target = document.querySelector('#note_1234');
@@ -28,6 +29,7 @@ describe('ResizeObserver Utility', () => {
afterEach(() => {
contentTop.mockReset();
+ resetHTMLFixture();
});
describe('Observer behavior', () => {
diff --git a/spec/frontend/listbox/index_spec.js b/spec/frontend/listbox/index_spec.js
index 45659a0e523..07c6cca535a 100644
--- a/spec/frontend/listbox/index_spec.js
+++ b/spec/frontend/listbox/index_spec.js
@@ -3,7 +3,7 @@ import { getAllByRole, getByRole } from '@testing-library/dom';
import { GlDropdown } from '@gitlab/ui';
import { createWrapper } from '@vue/test-utils';
import { initListbox, parseAttributes } from '~/listbox';
-import { getFixture, setHTMLFixture } from 'helpers/fixtures';
+import { getFixture, setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
jest.mock('~/lib/utils/url_utility');
@@ -63,6 +63,10 @@ describe('initListbox', () => {
await nextTick();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('returns an instance', () => {
expect(instance).not.toBe(null);
});
diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js
index 9229b353685..bcf64204c7a 100644
--- a/spec/frontend/merge_request_spec.js
+++ b/spec/frontend/merge_request_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -11,7 +12,7 @@ describe('MergeRequest', () => {
let mock;
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_with_task_list.html');
+ loadHTMLFixture('merge_requests/merge_request_with_task_list.html');
jest.spyOn(axios, 'patch');
mock = new MockAdapter(axios);
@@ -26,6 +27,7 @@ describe('MergeRequest', () => {
afterEach(() => {
mock.restore();
+ resetHTMLFixture();
});
it('modifies the Markdown field', async () => {
@@ -103,7 +105,7 @@ describe('MergeRequest', () => {
describe('hideCloseButton', () => {
describe('merge request of current_user', () => {
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_of_current_user.html');
+ loadHTMLFixture('merge_requests/merge_request_of_current_user.html');
test.el = document.querySelector('.js-issuable-actions');
MergeRequest.hideCloseButton();
});
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 5c24a070342..ccbc61ea658 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initMrPage from 'helpers/init_vue_mr_page_helper';
import axios from '~/lib/utils/axios_utils';
import MergeRequestTabs from '~/merge_request_tabs';
@@ -79,7 +80,7 @@ describe('MergeRequestTabs', () => {
let tabUrl;
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_with_task_list.html');
+ loadHTMLFixture('merge_requests/merge_request_with_task_list.html');
tabUrl = $('.commits-tab a').attr('href');
@@ -97,6 +98,10 @@ describe('MergeRequestTabs', () => {
};
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('meta click', () => {
let metakeyEvent;
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 7bd062b81f1..1f9eb03b5d4 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -65,6 +65,7 @@ describe('Dashboard Panel', () => {
},
store,
mocks,
+ provide: { glFeatures: { monitorLogging: true } },
...options,
});
};
@@ -379,6 +380,21 @@ describe('Dashboard Panel', () => {
expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
});
+ describe(':monitor_logging feature flag', () => {
+ it.each`
+ flagState | logsState | expected
+ ${true} | ${'shows'} | ${true}
+ ${false} | ${'hides'} | ${false}
+ `('$logsState logs when flag state is $flagState', async ({ flagState, expected }) => {
+ createWrapper({}, { provide: { glFeatures: { monitorLogging: flagState } } });
+ state.logsPath = mockLogsPath;
+ state.timeRange = mockTimeRange;
+ await nextTick();
+
+ expect(findViewLogsLink().exists()).toBe(expected);
+ });
+ });
+
it('it is overridden when a datazoom event is received', async () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
@@ -488,15 +504,7 @@ describe('Dashboard Panel', () => {
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
- wrapper = shallowMount(DashboardPanel, {
- propsData: {
- graphData,
- settingsPath: dashboardProps.settingsPath,
- namespace: mockNamespace,
- },
- store,
- mocks,
- });
+ createWrapper({ namespace: mockNamespace });
});
it('handles namespaced time range and logs path state', async () => {
diff --git a/spec/frontend/new_branch_spec.js b/spec/frontend/new_branch_spec.js
index 66b28a8c0dc..e4f4b3fa5b5 100644
--- a/spec/frontend/new_branch_spec.js
+++ b/spec/frontend/new_branch_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import NewBranchForm from '~/new_branch_form';
describe('Branch', () => {
@@ -18,11 +19,15 @@ describe('Branch', () => {
}
beforeEach(() => {
- loadFixtures('branches/new_branch.html');
+ loadHTMLFixture('branches/new_branch.html');
$('form').on('submit', (e) => e.preventDefault());
testContext.form = new NewBranchForm($('.js-create-branch-form'), []);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it("can't start with a dot", () => {
fillNameWith('.foo');
expectToHaveError("can't start with '.'");
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index a856d002d2e..b83e3b1b715 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => {
describe('has no discussions', () => {
it('does not render', () => {
- wrapper = shallowMount(DiscussionCounter, { store });
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => {
it('does not render', () => {
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store });
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -75,20 +75,33 @@ describe('DiscussionCounter component', () => {
it('renders', () => {
updateStore();
- wrapper = shallowMount(DiscussionCounter, { store });
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(true);
});
it.each`
- title | resolved | isActive | groupLength
- ${'not allResolved'} | ${false} | ${false} | ${3}
- ${'allResolved'} | ${true} | ${true} | ${1}
- `('renders correctly if $title', ({ resolved, isActive, groupLength }) => {
+ blocksMerge | color
+ ${true} | ${'gl-bg-orange-50'}
+ ${false} | ${'gl-bg-gray-50'}
+ `(
+ 'changes background color to $color if blocksMerge is $blocksMerge',
+ ({ blocksMerge, color }) => {
+ updateStore();
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge } });
+
+ expect(wrapper.find('[data-testid="discussions-counter-text"]').classes()).toContain(color);
+ },
+ );
+
+ it.each`
+ title | resolved | groupLength
+ ${'not allResolved'} | ${false} | ${4}
+ ${'allResolved'} | ${true} | ${1}
+ `('renders correctly if $title', ({ resolved, groupLength }) => {
updateStore({ resolvable: true, resolved });
- wrapper = shallowMount(DiscussionCounter, { store });
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
- expect(wrapper.find(`.is-active`).exists()).toBe(isActive);
expect(wrapper.findAll(GlButton)).toHaveLength(groupLength);
});
});
@@ -99,7 +112,7 @@ describe('DiscussionCounter component', () => {
const discussion = { ...discussionMock, expanded };
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store });
+ wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } });
toggleAllButton = wrapper.find('.toggle-all-discussions-btn');
};
@@ -117,26 +130,26 @@ describe('DiscussionCounter component', () => {
updateStoreWithExpanded(true);
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('angle-up');
+ expect(toggleAllButton.props('icon')).toBe('collapse');
toggleAllButton.vm.$emit('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('angle-down');
+ expect(toggleAllButton.props('icon')).toBe('expand');
});
it('expands all discussions if collapsed', async () => {
updateStoreWithExpanded(false);
expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('angle-down');
+ expect(toggleAllButton.props('icon')).toBe('expand');
toggleAllButton.vm.$emit('click');
await nextTick();
expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('angle-up');
+ expect(toggleAllButton.props('icon')).toBe('collapse');
});
});
});
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index e227af88d3f..413ee815906 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -2,6 +2,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import setWindowLocation from 'helpers/set_window_location_helper';
import { setTestTimeout } from 'helpers/timeout';
import waitForPromises from 'helpers/wait_for_promises';
@@ -92,13 +93,17 @@ describe('note_app', () => {
describe('set data', () => {
beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
+ setHTMLFixture('<div class="js-discussions-count"></div>');
axiosMock.onAny().reply(200, []);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should set notes data', () => {
expect(store.state.notesData).toEqual(mockData.notesDataMock);
});
@@ -122,13 +127,17 @@ describe('note_app', () => {
describe('render', () => {
beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
+ setHTMLFixture('<div class="js-discussions-count"></div>');
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should render list of notes', () => {
const note =
mockData.INDIVIDUAL_NOTE_RESPONSE_MAP.GET[
@@ -160,7 +169,7 @@ describe('note_app', () => {
describe('render with comments disabled', () => {
beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
+ setHTMLFixture('<div class="js-discussions-count"></div>');
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
store.state.commentsDisabled = true;
@@ -168,6 +177,10 @@ describe('note_app', () => {
return waitForDiscussionsRequest();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should not render form when commenting is disabled', () => {
expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
@@ -179,7 +192,7 @@ describe('note_app', () => {
describe('timeline view', () => {
beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
+ setHTMLFixture('<div class="js-discussions-count"></div>');
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
store.state.commentsDisabled = false;
@@ -189,6 +202,10 @@ describe('note_app', () => {
return waitForDiscussionsRequest();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should not render comments form', () => {
expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
@@ -196,12 +213,15 @@ describe('note_app', () => {
describe('while fetching data', () => {
beforeEach(() => {
- setFixtures('<div class="js-discussions-count"></div>');
+ setHTMLFixture('<div class="js-discussions-count"></div>');
axiosMock.onAny().reply(200, []);
wrapper = mountComponent();
});
- afterEach(() => waitForDiscussionsRequest());
+ afterEach(() => {
+ waitForDiscussionsRequest();
+ resetHTMLFixture();
+ });
it('renders skeleton notes', () => {
expect(wrapper.find('.animation-container').exists()).toBe(true);
diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js
index 7193475c96a..40b124b9029 100644
--- a/spec/frontend/notes/deprecated_notes_spec.js
+++ b/spec/frontend/notes/deprecated_notes_spec.js
@@ -3,6 +3,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { createSpyObj } from 'helpers/jest_helpers';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
@@ -33,7 +34,7 @@ gl.utils.disableButtonIfEmptyField = () => {};
// eslint-disable-next-line jest/no-disabled-tests
describe.skip('Old Notes (~/deprecated_notes.js)', () => {
beforeEach(() => {
- loadFixtures(fixture);
+ loadHTMLFixture(fixture);
// Re-declare this here so that test_setup.js#beforeEach() doesn't
// overwrite it.
@@ -50,12 +51,14 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
setTestTimeoutOnce(4000);
});
- afterEach(() => {
+ afterEach(async () => {
// The Notes component sets a polling interval. Clear it after every run.
// Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
jest.clearAllTimers();
- return axios.waitForAll().finally(() => mockAxios.restore());
+ await axios.waitForAll().finally(() => mockAxios.restore());
+
+ resetHTMLFixture();
});
it('loads the Notes class into the DOM', () => {
@@ -629,7 +632,7 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
let $notesContainer;
beforeEach(() => {
- loadFixtures('commit/show.html');
+ loadHTMLFixture('commit/show.html');
mockAxios.onPost(NOTES_POST_PATH).reply(200, note);
new Notes('', []);
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 75e7756cd6b..75a53c61ee6 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -1,4 +1,5 @@
import AxiosMockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
@@ -51,7 +52,7 @@ describe('Actions Notes Store', () => {
axiosMock = new AxiosMockAdapter(axios);
// This is necessary as we query Close issue button at the top of issue page when clicking bottom button
- setFixtures(
+ setHTMLFixture(
'<div class="detail-page-header-actions"><button class="btn-close btn-grouped"></button></div>',
);
});
@@ -59,6 +60,7 @@ describe('Actions Notes Store', () => {
afterEach(() => {
resetStore(store);
axiosMock.restore();
+ resetHTMLFixture();
});
describe('setNotesData', () => {
diff --git a/spec/frontend/oauth_remember_me_spec.js b/spec/frontend/oauth_remember_me_spec.js
index 3187cbf6547..1fa0e0aa8f6 100644
--- a/spec/frontend/oauth_remember_me_spec.js
+++ b/spec/frontend/oauth_remember_me_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
@@ -7,11 +8,15 @@ describe('OAuthRememberMe', () => {
};
beforeEach(() => {
- loadFixtures('static/oauth_remember_me.html');
+ loadHTMLFixture('static/oauth_remember_me.html');
new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('adds the "remember_me" query parameter to all OAuth login buttons', () => {
$('#oauth-container #remember_me').click();
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index 9df69124d66..dfb3e87a342 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -26,12 +27,14 @@ describe('pager', () => {
const originalHref = window.location.href;
beforeEach(() => {
- setFixtures('<div class="content_list"></div><div class="loading"></div>');
+ setHTMLFixture('<div class="content_list"></div><div class="loading"></div>');
jest.spyOn($.fn, 'endlessScroll').mockImplementation();
});
afterEach(() => {
window.history.replaceState({}, null, originalHref);
+
+ resetHTMLFixture();
});
it('should get initial offset from query parameter', () => {
@@ -57,7 +60,7 @@ describe('pager', () => {
}
beforeEach(() => {
- setFixtures(
+ setHTMLFixture(
'<div class="content_list" data-href="/some_list"></div><div class="loading"></div>',
);
jest.spyOn(axios, 'get');
@@ -65,6 +68,10 @@ describe('pager', () => {
Pager.init();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('shows loader while loading next page', async () => {
mockSuccess();
@@ -135,7 +142,11 @@ describe('pager', () => {
const href = `${TEST_HOST}/some_list.json`;
beforeEach(() => {
- setFixtures(`<div class="content_list" data-href="${href}"></div>`);
+ setHTMLFixture(`<div class="content_list" data-href="${href}"></div>`);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('should use data-href attribute', () => {
@@ -154,7 +165,11 @@ describe('pager', () => {
describe('no data-href attribute attribute provided from list element', () => {
beforeEach(() => {
- setFixtures(`<div class="content_list"></div>`);
+ setHTMLFixture(`<div class="content_list"></div>`);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('should use current url', () => {
@@ -190,7 +205,7 @@ describe('pager', () => {
describe('when `container` is visible', () => {
it('makes API request', () => {
- setFixtures(
+ setHTMLFixture(
`<div id="js-pager"><div class="content_list" data-href="${href}"></div></div>`,
);
@@ -199,12 +214,14 @@ describe('pager', () => {
endlessScrollCallback();
expect(axios.get).toHaveBeenCalledWith(href, expect.any(Object));
+
+ resetHTMLFixture();
});
});
describe('when `container` is not visible', () => {
it('does not make API request', () => {
- setFixtures(
+ setHTMLFixture(
`<div id="js-pager" style="display: none;"><div class="content_list" data-href="${href}"></div></div>`,
);
@@ -213,6 +230,8 @@ describe('pager', () => {
endlessScrollCallback();
expect(axios.get).not.toHaveBeenCalled();
+
+ resetHTMLFixture();
});
});
});
diff --git a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
index 71c9da238b4..6edfe9641b9 100644
--- a/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
+++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import '~/lib/utils/text_utility';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
describe('Abuse Reports', () => {
@@ -15,11 +15,15 @@ describe('Abuse Reports', () => {
$messages.filter((index, element) => element.innerText.indexOf(searchText) > -1).first();
beforeEach(() => {
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
new AbuseReports(); // eslint-disable-line no-new
$messages = $('.abuse-reports .message');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should truncate long messages', () => {
const $longMessage = findMessage('LONG MESSAGE');
diff --git a/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
index 3a4f93d4464..542eb2f3ab8 100644
--- a/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
+++ b/spec/frontend/pages/admin/application_settings/account_and_limits_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initUserInternalRegexPlaceholder, {
PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE,
@@ -10,12 +11,16 @@ describe('AccountAndLimits', () => {
let $userInternalRegex;
beforeEach(() => {
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
initUserInternalRegexPlaceholder();
$userDefaultExternal = $('#application_setting_user_default_external');
$userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('Changing of userInternalRegex when userDefaultExternal', () => {
it('is unchecked', () => {
expect($userDefaultExternal.prop('checked')).toBeFalsy();
diff --git a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
index 4140b985682..3a52c243867 100644
--- a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
+++ b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js
@@ -2,6 +2,7 @@ import initSetHelperText, {
HELPER_TEXT_SERVICE_PING_DISABLED,
HELPER_TEXT_SERVICE_PING_ENABLED,
} from '~/pages/admin/application_settings/metrics_and_profiling/usage_statistics';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
describe('UsageStatistics', () => {
const FIXTURE = 'application_settings/usage.html';
@@ -11,7 +12,7 @@ describe('UsageStatistics', () => {
let servicePingFeaturesHelperText;
beforeEach(() => {
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
initSetHelperText();
servicePingCheckBox = document.getElementById('application_setting_usage_ping_enabled');
servicePingFeaturesCheckBox = document.getElementById(
@@ -21,6 +22,10 @@ describe('UsageStatistics', () => {
servicePingFeaturesHelperText = document.getElementById('service_ping_features_helper_text');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const expectEnabledservicePingFeaturesCheckBox = () => {
expect(servicePingFeaturesCheckBox.classList.contains('gl-cursor-not-allowed')).toBe(false);
expect(servicePingFeaturesHelperText.textContent).toEqual(HELPER_TEXT_SERVICE_PING_ENABLED);
diff --git a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
index f10b202f4d7..909349569a8 100644
--- a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
+++ b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import Api from '~/api';
import NamespaceSelect from '~/pages/admin/projects/components/namespace_select.vue';
@@ -26,7 +27,7 @@ describe('Dropdown select component', () => {
};
beforeEach(() => {
- setFixtures('<div class="test-container"></div>');
+ setHTMLFixture('<div class="test-container"></div>');
jest.spyOn(Api, 'namespaces').mockImplementation((_, callback) =>
callback([
@@ -36,6 +37,10 @@ describe('Dropdown select component', () => {
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('creates a hidden input if fieldName is provided', () => {
mountDropdown({ fieldName: 'namespace-input' });
diff --git a/spec/frontend/pages/dashboard/todos/index/todos_spec.js b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
index ae53afa7fba..3a9b59f291c 100644
--- a/spec/frontend/pages/dashboard/todos/index/todos_spec.js
+++ b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
@@ -19,7 +20,7 @@ describe('Todos', () => {
let mock;
beforeEach(() => {
- loadFixtures('todos/todos.html');
+ loadHTMLFixture('todos/todos.html');
todoItem = document.querySelector('.todos-list .todo');
mock = new MockAdapter(axios);
@@ -27,6 +28,10 @@ describe('Todos', () => {
});
afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ afterEach(() => {
mock.restore();
});
diff --git a/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js b/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js
index ea49111760b..5c186441817 100644
--- a/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js
+++ b/spec/frontend/pages/projects/merge_requests/edit/check_form_state_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'jest/__helpers__/fixtures';
import initCheckFormState from '~/pages/projects/merge_requests/edit/check_form_state';
describe('Check form state', () => {
@@ -7,7 +8,7 @@ describe('Check form state', () => {
let setDialogContent;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<form class="merge-request-form">
<input type="text" name="test" id="form-input"/>
</form>`);
@@ -22,6 +23,8 @@ describe('Check form state', () => {
afterEach(() => {
beforeUnloadEvent.preventDefault.mockRestore();
setDialogContent.mockRestore();
+
+ resetHTMLFixture();
});
it('shows confirmation dialog when there are unsaved changes', () => {
diff --git a/spec/frontend/pages/projects/pages_domains/form_spec.js b/spec/frontend/pages/projects/pages_domains/form_spec.js
index 55336596f30..e437121acd2 100644
--- a/spec/frontend/pages/projects/pages_domains/form_spec.js
+++ b/spec/frontend/pages/projects/pages_domains/form_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initForm from '~/pages/projects/pages_domains/form';
const ENABLED_UNLESS_AUTO_SSL_CLASS = 'js-enabled-unless-auto-ssl';
@@ -17,7 +18,7 @@ describe('Page domains form', () => {
const findUnlessAutoSsl = () => document.querySelector(`.${SHOW_UNLESS_AUTO_SSL_CLASS}`);
const create = () => {
- setFixtures(`
+ setHTMLFixture(`
<form>
<span
class="${SSL_TOGGLE_CLASS}"
@@ -31,6 +32,10 @@ describe('Page domains form', () => {
`);
};
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('instantiates the toggle', () => {
create();
initForm();
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
index b700c255e8c..42eeff89bf4 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/timezone_dropdown_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import TimezoneDropdown, {
formatUtcOffset,
formatTimezone,
@@ -25,13 +26,17 @@ describe('Timezone Dropdown', () => {
describe('Initialize', () => {
describe('with dropdown already loaded', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html');
+ loadHTMLFixture('pipeline_schedules/edit.html');
$wrapper = $('.dropdown');
$inputEl = $('#schedule_cron_timezone');
$inputEl.val('');
$dropdownEl = $('.js-timezone-dropdown');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('can take an $inputEl in the constructor', () => {
initTimezoneDropdown();
@@ -86,7 +91,7 @@ describe('Timezone Dropdown', () => {
describe('without dropdown loaded', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html');
+ loadHTMLFixture('pipeline_schedules/edit.html');
$wrapper = $('.dropdown');
$inputEl = $('#schedule_cron_timezone');
$dropdownEl = $('.js-timezone-dropdown');
diff --git a/spec/frontend/pages/search/show/refresh_counts_spec.js b/spec/frontend/pages/search/show/refresh_counts_spec.js
index 81c9bf74308..6f14f0c70bd 100644
--- a/spec/frontend/pages/search/show/refresh_counts_spec.js
+++ b/spec/frontend/pages/search/show/refresh_counts_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
import refreshCounts from '~/pages/search/show/refresh_counts';
@@ -18,7 +19,11 @@ describe('pages/search/show/refresh_counts', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- setFixtures(fixture);
+ setHTMLFixture(fixture);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
afterEach(() => {
diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
index a29db961452..4c4a0fbea11 100644
--- a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
+++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
describe('preserve_url_fragment', () => {
@@ -7,7 +8,11 @@ describe('preserve_url_fragment', () => {
};
beforeEach(() => {
- loadFixtures('sessions/new.html');
+ loadHTMLFixture('sessions/new.html');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('adds the url fragment to the login form actions', () => {
diff --git a/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js b/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js
index 601fcfedbe0..f736ce46f9b 100644
--- a/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js
+++ b/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js
@@ -1,3 +1,4 @@
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import AccessorUtilities from '~/lib/utils/accessor';
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
@@ -19,11 +20,15 @@ describe('SigninTabsMemoizer', () => {
}
beforeEach(() => {
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(true);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('does nothing if no tab was previously selected', () => {
createMemoizer();
diff --git a/spec/frontend/performance_bar/index_spec.js b/spec/frontend/performance_bar/index_spec.js
index 91cb46002be..6c1cbfa70a1 100644
--- a/spec/frontend/performance_bar/index_spec.js
+++ b/spec/frontend/performance_bar/index_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import '~/performance_bar/components/performance_bar_app.vue';
import performanceBar from '~/performance_bar';
@@ -11,7 +12,7 @@ describe('performance bar wrapper', () => {
let vm;
beforeEach(() => {
- setFixtures('<div id="js-peek"></div>');
+ setHTMLFixture('<div id="js-peek"></div>');
const peekWrapper = document.getElementById('js-peek');
performance.getEntriesByType = jest.fn().mockReturnValue([]);
@@ -49,6 +50,7 @@ describe('performance bar wrapper', () => {
vm.$destroy();
document.getElementById('js-peek').remove();
mock.restore();
+ resetHTMLFixture();
});
describe('addRequest', () => {
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index b0203db70de..89002ee47a8 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -33,9 +33,7 @@ describe('The Pipeline Tabs', () => {
...defaultProvide,
},
stubs: {
- Dag: { template: '<div id="dag"/>' },
JobsApp: { template: '<div class="jobs" />' },
- PipelineGraph: { template: '<div id="graph" />' },
TestReports: { template: '<div id="tests" />' },
},
}),
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
index be422fac92c..2c6d126e12c 100644
--- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import { parseData } from '~/pipelines/components/parsing_utils';
import { createJobsHash } from '~/pipelines/utils';
@@ -42,7 +42,7 @@ describe('Links Inner component', () => {
// We create fixture so that each job has an empty div that represent
// the JobPill in the DOM. Each `JobPill` would have different coordinates,
// so we increment their coordinates on each iteration to simulate different positions.
- const setFixtures = ({ stages }) => {
+ const setHTMLFixtureLocal = ({ stages }) => {
const jobs = createJobsHash(stages);
const arrayOfJobs = Object.keys(jobs);
@@ -82,6 +82,7 @@ describe('Links Inner component', () => {
afterEach(() => {
jest.restoreAllMocks();
wrapper.destroy();
+ resetHTMLFixture();
});
describe('basic SVG creation', () => {
@@ -124,7 +125,7 @@ describe('Links Inner component', () => {
describe('with one need', () => {
beforeEach(() => {
- setFixtures(pipelineData);
+ setHTMLFixtureLocal(pipelineData);
createComponent({ pipelineData: pipelineData.stages });
});
@@ -143,7 +144,7 @@ describe('Links Inner component', () => {
describe('with a parallel need', () => {
beforeEach(() => {
- setFixtures(parallelNeedData);
+ setHTMLFixtureLocal(parallelNeedData);
createComponent({ pipelineData: parallelNeedData.stages });
});
@@ -162,7 +163,7 @@ describe('Links Inner component', () => {
describe('with same stage needs', () => {
beforeEach(() => {
- setFixtures(sameStageNeeds);
+ setHTMLFixtureLocal(sameStageNeeds);
createComponent({ pipelineData: sameStageNeeds.stages });
});
@@ -181,7 +182,7 @@ describe('Links Inner component', () => {
describe('with a large number of needs', () => {
beforeEach(() => {
- setFixtures(largePipelineData);
+ setHTMLFixtureLocal(largePipelineData);
createComponent({ pipelineData: largePipelineData.stages });
});
@@ -200,7 +201,7 @@ describe('Links Inner component', () => {
describe('interactions', () => {
beforeEach(() => {
- setFixtures(largePipelineData);
+ setHTMLFixtureLocal(largePipelineData);
createComponent({ pipelineData: largePipelineData.stages });
});
diff --git a/spec/frontend/project_select_combo_button_spec.js b/spec/frontend/project_select_combo_button_spec.js
index 1f762d359ea..b8d5a1a61f3 100644
--- a/spec/frontend/project_select_combo_button_spec.js
+++ b/spec/frontend/project_select_combo_button_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import ProjectSelectComboButton from '~/project_select_combo_button';
const fixturePath = 'static/project_select_combo_button.html';
@@ -31,12 +32,16 @@ describe('Project Select Combo Button', () => {
relativePath: 'issues/new',
};
- loadFixtures(fixturePath);
+ loadHTMLFixture(fixturePath);
testContext.newItemBtn = document.querySelector('.js-new-project-item-link');
testContext.projectSelectInput = document.querySelector('.project-item-select');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('on page load when localStorage is empty', () => {
beforeEach(() => {
testContext.comboButton = new ProjectSelectComboButton(testContext.projectSelectInput);
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 4e567ab030e..d11090cba8a 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -2,6 +2,7 @@ import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
@@ -30,7 +31,7 @@ describe('Author Select', () => {
let wrapper;
const createComponent = () => {
- setFixtures(`
+ setHTMLFixture(`
<div class="js-project-commits-show">
<input id="commits-search" type="text" />
<div id="commits-list"></div>
@@ -54,6 +55,7 @@ describe('Author Select', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
const findDropdownContainer = () => wrapper.find({ ref: 'dropdownContainer' });
diff --git a/spec/frontend/projects/new/components/deployment_target_select_spec.js b/spec/frontend/projects/new/components/deployment_target_select_spec.js
index 1c443879dc3..f3b22d4a1b9 100644
--- a/spec/frontend/projects/new/components/deployment_target_select_spec.js
+++ b/spec/frontend/projects/new/components/deployment_target_select_spec.js
@@ -1,6 +1,7 @@
import { GlFormGroup, GlFormSelect, GlFormText, GlLink, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mockTracking } from 'helpers/tracking_helper';
import DeploymentTargetSelect from '~/projects/new/components/deployment_target_select.vue';
import {
@@ -32,7 +33,7 @@ describe('Deployment target select', () => {
};
const createForm = () => {
- setFixtures(`
+ setHTMLFixture(`
<form id="${NEW_PROJECT_FORM}">
</form>
`);
@@ -47,6 +48,7 @@ describe('Deployment target select', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
it('renders the correct label', () => {
diff --git a/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
index 31ddbc80ae4..42259a5c392 100644
--- a/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
+++ b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
@@ -1,5 +1,6 @@
import { GlPopover, GlFormInputGroup } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import NewProjectPushTipPopover from '~/projects/new/components/new_project_push_tip_popover.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@@ -31,12 +32,13 @@ describe('New project push tip popover', () => {
};
beforeEach(() => {
- setFixtures(`<a id="${targetId}"></a>`);
+ setHTMLFixture(`<a id="${targetId}"></a>`);
buildWrapper();
});
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
it('renders popover that targets the specified target', () => {
diff --git a/spec/frontend/projects/project_import_gitlab_project_spec.js b/spec/frontend/projects/project_import_gitlab_project_spec.js
index aaf8a81f626..76621ba9c06 100644
--- a/spec/frontend/projects/project_import_gitlab_project_spec.js
+++ b/spec/frontend/projects/project_import_gitlab_project_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import projectImportGitlab from '~/projects/project_import_gitlab_project';
describe('Import Gitlab project', () => {
@@ -7,7 +8,7 @@ describe('Import Gitlab project', () => {
const setTestFixtures = (url) => {
window.history.pushState({}, null, url);
- setFixtures(`
+ setHTMLFixture(`
<input class="js-path-name" />
<input class="js-project-name" />
`);
@@ -21,6 +22,7 @@ describe('Import Gitlab project', () => {
afterEach(() => {
window.history.pushState({}, null, '');
+ resetHTMLFixture();
});
describe('project name', () => {
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index d2936cb9efe..fe325343da8 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import projectNew from '~/projects/project_new';
@@ -8,7 +9,7 @@ describe('New Project', () => {
let $projectName;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class='toggle-import-form'>
<div class='import-url-data'>
<div class="form-group">
@@ -33,6 +34,10 @@ describe('New Project', () => {
$projectName = $('#project_name');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('deriveProjectPathFromUrl', () => {
const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
diff --git a/spec/frontend/projects/projects_filterable_list_spec.js b/spec/frontend/projects/projects_filterable_list_spec.js
index a41e8b7bc09..f217efa411e 100644
--- a/spec/frontend/projects/projects_filterable_list_spec.js
+++ b/spec/frontend/projects/projects_filterable_list_spec.js
@@ -1,4 +1,4 @@
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import ProjectsFilterableList from '~/projects/projects_filterable_list';
describe('ProjectsFilterableList', () => {
@@ -20,6 +20,10 @@ describe('ProjectsFilterableList', () => {
List = new ProjectsFilterableList(form, filter, holder);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('getFilterEndpoint', () => {
it('updates converts getPagePath for projects', () => {
jest.spyOn(List, 'getPagePath').mockReturnValue('blah/projects?');
diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js
index 236968a3736..65b01172e7e 100644
--- a/spec/frontend/projects/settings/access_dropdown_spec.js
+++ b/spec/frontend/projects/settings/access_dropdown_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import AccessDropdown from '~/projects/settings/access_dropdown';
import { LEVEL_TYPES } from '~/projects/settings/constants';
@@ -7,7 +8,7 @@ describe('AccessDropdown', () => {
let dropdown;
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div id="dummy-dropdown">
<span class="dropdown-toggle-text"></span>
</div>
@@ -28,6 +29,10 @@ describe('AccessDropdown', () => {
dropdown = new AccessDropdown(options);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('toggleLabel', () => {
let $dropdownToggleText;
const dummyItems = [
diff --git a/spec/frontend/prometheus_metrics/custom_metrics_spec.js b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
index 20593351ee5..473327bf5e1 100644
--- a/spec/frontend/prometheus_metrics/custom_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/custom_metrics_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import PANEL_STATE from '~/prometheus_metrics/constants';
import CustomMetrics from '~/prometheus_metrics/custom_metrics';
@@ -15,11 +16,12 @@ describe('PrometheusMetrics', () => {
mock.onGet(customMetricsEndpoint).reply(200, {
metrics,
});
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
});
afterEach(() => {
mock.restore();
+ resetHTMLFixture();
});
describe('Custom Metrics', () => {
diff --git a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
index ee74e28ba23..1151c0b3769 100644
--- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import PANEL_STATE from '~/prometheus_metrics/constants';
@@ -9,7 +10,7 @@ describe('PrometheusMetrics', () => {
const FIXTURE = 'services/prometheus/prometheus_service.html';
beforeEach(() => {
- loadFixtures(FIXTURE);
+ loadHTMLFixture(FIXTURE);
});
describe('constructor', () => {
@@ -19,6 +20,10 @@ describe('PrometheusMetrics', () => {
prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should initialize wrapper element refs on class object', () => {
expect(prometheusMetrics.$wrapper).toBeDefined();
expect(prometheusMetrics.$monitoredMetricsPanel).toBeDefined();
diff --git a/spec/frontend/protected_branches/protected_branch_create_spec.js b/spec/frontend/protected_branches/protected_branch_create_spec.js
index b3de2d5e031..4b634c52b01 100644
--- a/spec/frontend/protected_branches/protected_branch_create_spec.js
+++ b/spec/frontend/protected_branches/protected_branch_create_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
const FORCE_PUSH_TOGGLE_TESTID = 'force-push-toggle';
@@ -21,7 +22,7 @@ describe('ProtectedBranchCreate', () => {
codeOwnerToggleChecked = false,
hasLicense = true,
} = {}) => {
- setFixtures(`
+ setHTMLFixture(`
<form class="js-new-protected-branch">
<span
class="js-force-push-toggle"
@@ -40,6 +41,10 @@ describe('ProtectedBranchCreate', () => {
return new ProtectedBranchCreate({ hasLicense });
};
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('when license supports code owner approvals', () => {
it('instantiates the code owner toggle', () => {
create();
diff --git a/spec/frontend/protected_branches/protected_branch_edit_spec.js b/spec/frontend/protected_branches/protected_branch_edit_spec.js
index 959ca6ecde2..d842e00d850 100644
--- a/spec/frontend/protected_branches/protected_branch_edit_spec.js
+++ b/spec/frontend/protected_branches/protected_branch_edit_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -33,7 +34,7 @@ describe('ProtectedBranchEdit', () => {
codeOwnerToggleChecked = false,
hasLicense = true,
} = {}) => {
- setFixtures(`<div id="wrap" data-url="${TEST_URL}">
+ setHTMLFixture(`<div id="wrap" data-url="${TEST_URL}">
<span
class="js-force-push-toggle"
data-label="Toggle allowed to force push"
@@ -51,6 +52,7 @@ describe('ProtectedBranchEdit', () => {
afterEach(() => {
mock.restore();
+ resetHTMLFixture();
});
describe('when license supports code owner approvals', () => {
@@ -76,7 +78,11 @@ describe('ProtectedBranchEdit', () => {
describe('when toggles are not available in the DOM on page load', () => {
beforeEach(() => {
create({ hasLicense: true });
- setFixtures('');
+ setHTMLFixture('');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('does not instantiate the force push toggle', () => {
diff --git a/spec/frontend/read_more_spec.js b/spec/frontend/read_more_spec.js
index 16f0d7fb075..80d7c941660 100644
--- a/spec/frontend/read_more_spec.js
+++ b/spec/frontend/read_more_spec.js
@@ -1,10 +1,15 @@
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
const fixtureName = 'projects/overview.html';
beforeEach(() => {
- loadFixtures(fixtureName);
+ loadHTMLFixture(fixtureName);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
describe('expands target element', () => {
diff --git a/spec/frontend/right_sidebar_spec.js b/spec/frontend/right_sidebar_spec.js
index d1f861669a0..5847842f5a6 100644
--- a/spec/frontend/right_sidebar_spec.js
+++ b/spec/frontend/right_sidebar_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
-import '~/commons/bootstrap';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import Sidebar from '~/right_sidebar';
@@ -30,7 +30,7 @@ describe('RightSidebar', () => {
let mock;
beforeEach(() => {
- loadFixtures(fixtureName);
+ loadHTMLFixture(fixtureName);
mock = new MockAdapter(axios);
new Sidebar(); // eslint-disable-line no-new
$aside = $('.right-sidebar');
@@ -44,6 +44,8 @@ describe('RightSidebar', () => {
afterEach(() => {
mock.restore();
+
+ resetHTMLFixture();
});
it('should expand/collapse the sidebar when arrow is clicked', () => {
diff --git a/spec/frontend/search/highlight_blob_search_result_spec.js b/spec/frontend/search/highlight_blob_search_result_spec.js
index 9fa3bfc1f9a..15cff436076 100644
--- a/spec/frontend/search/highlight_blob_search_result_spec.js
+++ b/spec/frontend/search/highlight_blob_search_result_spec.js
@@ -1,10 +1,15 @@
import setHighlightClass from '~/search/highlight_blob_search_result';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
const fixture = 'search/blob_search_result.html';
const searchKeyword = 'Send'; // spec/frontend/fixtures/search.rb#79
describe('search/highlight_blob_search_result', () => {
- beforeEach(() => loadFixtures(fixture));
+ beforeEach(() => loadHTMLFixture(fixture));
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
it('highlights lines with search term occurrence', () => {
setHighlightClass(searchKeyword);
diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js
index 190f2803324..4639552b4d3 100644
--- a/spec/frontend/search_autocomplete_spec.js
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -1,5 +1,6 @@
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import axios from '~/lib/utils/axios_utils';
import initSearchAutocomplete from '~/search_autocomplete';
@@ -104,7 +105,7 @@ describe('Search autocomplete dropdown', () => {
};
beforeEach(() => {
- loadFixtures('static/search_autocomplete.html');
+ loadHTMLFixture('static/search_autocomplete.html');
window.gon = {};
window.gon.current_user_id = userId;
@@ -118,6 +119,8 @@ describe('Search autocomplete dropdown', () => {
// Undo what we did to the shared <body>
removeBodyAttributes();
window.gon = {};
+
+ resetHTMLFixture();
});
it('should show Dashboard specific dropdown menu', () => {
diff --git a/spec/frontend/settings_panels_spec.js b/spec/frontend/settings_panels_spec.js
index 3a62cd703ab..d59e1a20b27 100644
--- a/spec/frontend/settings_panels_spec.js
+++ b/spec/frontend/settings_panels_spec.js
@@ -1,9 +1,14 @@
import $ from 'jquery';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import initSettingsPanels, { isExpanded } from '~/settings_panels';
describe('Settings Panels', () => {
beforeEach(() => {
- loadFixtures('groups/edit.html');
+ loadHTMLFixture('groups/edit.html');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
describe('initSettingsPane', () => {
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index 8b9a11056f2..e859d435f48 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { flatten } from 'lodash';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
const mockMousetrap = {
@@ -21,7 +22,7 @@ describe('Shortcuts', () => {
});
beforeEach(() => {
- loadFixtures(fixtureName);
+ loadHTMLFixture(fixtureName);
jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus');
jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus');
@@ -30,6 +31,10 @@ describe('Shortcuts', () => {
new Shortcuts(); // eslint-disable-line no-new
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('toggleMarkdownPreview', () => {
it('focuses preview button in form', () => {
Shortcuts.toggleMarkdownPreview(
diff --git a/spec/frontend/single_file_diff_spec.js b/spec/frontend/single_file_diff_spec.js
index 8718152655f..6f42ec47458 100644
--- a/spec/frontend/single_file_diff_spec.js
+++ b/spec/frontend/single_file_diff_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import SingleFileDiff from '~/single_file_diff';
@@ -15,6 +15,7 @@ describe('SingleFileDiff', () => {
afterEach(() => {
mock.restore();
+ resetHTMLFixture();
});
it('loads diff via axios exactly once for collapsed diffs', async () => {
diff --git a/spec/frontend/smart_interval_spec.js b/spec/frontend/smart_interval_spec.js
index 1a2fd7ff8f1..5dda097ae6a 100644
--- a/spec/frontend/smart_interval_spec.js
+++ b/spec/frontend/smart_interval_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import { assignIn } from 'lodash';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import waitForPromises from 'helpers/wait_for_promises';
import SmartInterval from '~/smart_interval';
@@ -116,11 +117,15 @@ describe('SmartInterval', () => {
describe('DOM Events', () => {
beforeEach(() => {
// This ensures DOM and DOM events are initialized for these specs.
- setFixtures('<div></div>');
+ setHTMLFixture('<div></div>');
interval = createDefaultSmartInterval();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should pause when page is not visible', () => {
jest.runOnlyPendingTimers();
diff --git a/spec/frontend/snippet/collapsible_input_spec.js b/spec/frontend/snippet/collapsible_input_spec.js
index 3f14a9cd1a1..56e64d136c2 100644
--- a/spec/frontend/snippet/collapsible_input_spec.js
+++ b/spec/frontend/snippet/collapsible_input_spec.js
@@ -1,4 +1,4 @@
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import setupCollapsibleInputs from '~/snippet/collapsible_input';
describe('~/snippet/collapsible_input', () => {
@@ -38,6 +38,10 @@ describe('~/snippet/collapsible_input', () => {
setupCollapsibleInputs();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const findInput = (el) => el.querySelector('textarea,input');
const findCollapsed = (el) => el.querySelector('.js-collapsed');
const findExpanded = (el) => el.querySelector('.js-expanded');
diff --git a/spec/frontend/syntax_highlight_spec.js b/spec/frontend/syntax_highlight_spec.js
index 8ad4f8d5c70..1be6c213350 100644
--- a/spec/frontend/syntax_highlight_spec.js
+++ b/spec/frontend/syntax_highlight_spec.js
@@ -1,6 +1,6 @@
/* eslint-disable no-return-assign */
-
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import syntaxHighlight from '~/syntax_highlight';
describe('Syntax Highlighter', () => {
@@ -20,7 +20,11 @@ describe('Syntax Highlighter', () => {
`('highlight using $desc syntax', ({ fn }) => {
describe('on a js-syntax-highlight element', () => {
beforeEach(() => {
- setFixtures('<div class="js-syntax-highlight"></div>');
+ setHTMLFixture('<div class="js-syntax-highlight"></div>');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
it('applies syntax highlighting', () => {
@@ -33,11 +37,15 @@ describe('Syntax Highlighter', () => {
describe('on a parent element', () => {
beforeEach(() => {
- setFixtures(
+ setHTMLFixture(
'<div class="parent">\n <div class="js-syntax-highlight"></div>\n <div class="foo"></div>\n <div class="js-syntax-highlight"></div>\n</div>',
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('applies highlighting to all applicable children', () => {
stubUserColorScheme('monokai');
syntaxHighlight(fn('.parent'));
@@ -49,7 +57,7 @@ describe('Syntax Highlighter', () => {
});
it('prevents an infinite loop when no matches exist', () => {
- setFixtures('<div></div>');
+ setHTMLFixture('<div></div>');
const highlight = () => syntaxHighlight(fn('div'));
expect(highlight).not.toThrow();
diff --git a/spec/frontend/tabs/index_spec.js b/spec/frontend/tabs/index_spec.js
index 98617b404ff..67e3d707adb 100644
--- a/spec/frontend/tabs/index_spec.js
+++ b/spec/frontend/tabs/index_spec.js
@@ -1,6 +1,6 @@
import { GlTabsBehavior, TAB_SHOWN_EVENT } from '~/tabs';
import { ACTIVE_PANEL_CLASS, ACTIVE_TAB_CLASSES } from '~/tabs/constants';
-import { getFixture, setHTMLFixture } from 'helpers/fixtures';
+import { getFixture, setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
const tabsFixture = getFixture('tabs/tabs.html');
@@ -93,6 +93,8 @@ describe('GlTabsBehavior', () => {
describe('when given an element', () => {
afterEach(() => {
glTabs.destroy();
+
+ resetHTMLFixture();
});
beforeEach(() => {
@@ -250,6 +252,10 @@ describe('GlTabsBehavior', () => {
glTabs = new GlTabsBehavior(tabsEl);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('connects the panels to their tabs correctly', () => {
findTab('bar').click();
diff --git a/spec/frontend/task_list_spec.js b/spec/frontend/task_list_spec.js
index fbdb73ae6de..e79c516a694 100644
--- a/spec/frontend/task_list_spec.js
+++ b/spec/frontend/task_list_spec.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import axios from '~/lib/utils/axios_utils';
import TaskList from '~/task_list';
@@ -14,7 +15,7 @@ describe('TaskList', () => {
const createTaskList = () => new TaskList(taskListOptions);
beforeEach(() => {
- setFixtures(`
+ setHTMLFixture(`
<div class="task-list">
<div class="js-task-list-container">
<ul data-sourcepos="5:1-5:11" class="task-list" dir="auto">
@@ -37,6 +38,10 @@ describe('TaskList', () => {
taskList = createTaskList();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should call init when the class constructed', () => {
jest.spyOn(TaskList.prototype, 'init');
jest.spyOn(TaskList.prototype, 'disable').mockImplementation(() => {});
diff --git a/spec/frontend/user_popovers_spec.js b/spec/frontend/user_popovers_spec.js
index 4edd526100a..fa598716645 100644
--- a/spec/frontend/user_popovers_spec.js
+++ b/spec/frontend/user_popovers_spec.js
@@ -1,5 +1,5 @@
import { within } from '@testing-library/dom';
-
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import UsersCache from '~/lib/utils/users_cache';
import initUserPopovers from '~/user_popovers';
import waitForPromises from 'helpers/wait_for_promises';
@@ -43,7 +43,7 @@ describe('User Popovers', () => {
};
beforeEach(() => {
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
const usersCacheSpy = () => Promise.resolve(dummyUser);
jest.spyOn(UsersCache, 'retrieveById').mockImplementation((userId) => usersCacheSpy(userId));
@@ -57,6 +57,10 @@ describe('User Popovers', () => {
popovers = initUserPopovers(document.querySelectorAll(selector));
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('initializes a popover for each user link with a user id', () => {
const linksWithUsers = findFixtureLinks();
diff --git a/spec/frontend/vue_alerts_spec.js b/spec/frontend/vue_alerts_spec.js
index 1952eea4a01..de2faa09438 100644
--- a/spec/frontend/vue_alerts_spec.js
+++ b/spec/frontend/vue_alerts_spec.js
@@ -1,5 +1,5 @@
import { nextTick } from 'vue';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import initVueAlerts from '~/vue_alerts';
@@ -40,6 +40,10 @@ describe('VueAlerts', () => {
);
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
const findJsHooks = () => document.querySelectorAll('.js-vue-alert');
const findAlerts = () => document.querySelectorAll('.gl-alert');
const findAlertDismiss = (alert) => alert.querySelector('.gl-dismiss-btn');
diff --git a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
index 59653a0ec13..e3d8bfd22ca 100644
--- a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
+++ b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
@@ -6,12 +6,16 @@ import { folder } from './mock_data';
describe('Deploy Board Instance', () => {
let wrapper;
- const createComponent = (props = {}) =>
+ const createComponent = (props = {}, provide) =>
shallowMount(DeployBoardInstance, {
propsData: {
status: 'succeeded',
...props,
},
+ provide: {
+ glFeatures: { monitorLogging: true },
+ ...provide,
+ },
});
describe('as a non-canary deployment', () => {
@@ -95,4 +99,23 @@ describe('Deploy Board Instance', () => {
expect(wrapper.attributes('title')).toEqual('');
});
});
+
+ describe(':monitor_logging feature flag', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ flagState | logsState | expected
+ ${true} | ${'shows'} | ${'/root/review-app/-/logs?environment_name=foo&pod_name=tanuki-1'}
+ ${false} | ${'hides'} | ${undefined}
+ `('$logsState log link when flag state is $flagState', async ({ flagState, expected }) => {
+ wrapper = createComponent(
+ { logsPath: folder.logs_path, podName: 'tanuki-1' },
+ { glFeatures: { monitorLogging: flagState } },
+ );
+
+ expect(wrapper.attributes('href')).toEqual(expected);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 8f05108d460..5cf891a2e52 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,5 +1,6 @@
import Mousetrap from 'mousetrap';
import Vue, { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { file } from 'jest/ide/helpers';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
@@ -22,7 +23,11 @@ describe('File finder item spec', () => {
}
beforeEach(() => {
- setFixtures('<div id="app"></div>');
+ setHTMLFixture('<div id="app"></div>');
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
});
afterEach(() => {
@@ -293,7 +298,7 @@ describe('File finder item spec', () => {
});
it('stops callback in monaco editor', () => {
- setFixtures('<div class="inputarea"></div>');
+ setHTMLFixture('<div class="inputarea"></div>');
expect(
Mousetrap.prototype.stopCallback(null, document.querySelector('.inputarea'), 't'),
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
index 3ceed670d77..9c29f304c71 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js
@@ -153,7 +153,11 @@ describe('DropdownContentsCreateView', () => {
});
it('enables a Create button', () => {
- expect(findCreateButton().props('disabled')).toBe(false);
+ expect(findCreateButton().props()).toMatchObject({
+ disabled: false,
+ category: 'primary',
+ variant: 'confirm',
+ });
});
it('renders a loader spinner after Create button click', async () => {
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index 20298e95107..a54f3450633 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -1,4 +1,5 @@
import { GlSkeletonLoader, GlIcon } from '@gitlab/ui';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
@@ -36,12 +37,13 @@ describe('User Popover Component', () => {
let wrapper;
beforeEach(() => {
- loadFixtures(fixtureTemplate);
+ loadHTMLFixture(fixtureTemplate);
gon.features = {};
});
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
const findUserStatus = () => wrapper.findByTestId('user-popover-status');
diff --git a/spec/frontend/vue_shared/directives/autofocusonshow_spec.js b/spec/frontend/vue_shared/directives/autofocusonshow_spec.js
index 59ce9f086c3..d052c99ec0e 100644
--- a/spec/frontend/vue_shared/directives/autofocusonshow_spec.js
+++ b/spec/frontend/vue_shared/directives/autofocusonshow_spec.js
@@ -1,3 +1,4 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
/**
@@ -10,10 +11,14 @@ describe('AutofocusOnShow directive', () => {
let el;
beforeEach(() => {
- setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>');
+ setHTMLFixture('<div id="container" style="display: none;"><input id="inputel"/></div>');
el = document.querySelector('#inputel');
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('should bind IntersectionObserver on input element', () => {
jest.spyOn(el, 'focus').mockImplementation(() => {});
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
index 7dfeced571a..a25f92c9cf2 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import IssuableBulkEditSidebar from '~/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue';
const createComponent = ({ expanded = true } = {}) =>
@@ -22,12 +23,13 @@ describe('IssuableBulkEditSidebar', () => {
let wrapper;
beforeEach(() => {
- setFixtures('<div class="layout-page right-sidebar-collapsed"></div>');
+ setHTMLFixture('<div class="layout-page right-sidebar-collapsed"></div>');
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
describe('watch', () => {
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
index 02579e8b3c4..544db891a13 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
@@ -1,6 +1,6 @@
import { GlIcon, GlAvatarLabeled } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
@@ -34,6 +34,7 @@ describe('IssuableHeader', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
describe('computed', () => {
@@ -47,7 +48,7 @@ describe('IssuableHeader', () => {
describe('handleRightSidebarToggleClick', () => {
beforeEach(() => {
- setFixtures('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>');
+ setHTMLFixture('<button class="js-toggle-right-sidebar-button">Collapse sidebar</button>');
});
it('dispatches `click` event on sidebar toggle button', () => {
diff --git a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
index 47bf3c8ed83..5b4d8718bbc 100644
--- a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
@@ -1,6 +1,7 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie';
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import IssuableSidebarRoot from '~/vue_shared/issuable/sidebar/components/issuable_sidebar_root.vue';
@@ -9,7 +10,7 @@ import { USER_COLLAPSED_GUTTER_COOKIE } from '~/vue_shared/issuable/sidebar/cons
const MOCK_LAYOUT_PAGE_CLASS = 'layout-page';
const createComponent = () => {
- setFixtures(`<div class="${MOCK_LAYOUT_PAGE_CLASS}"></div>`);
+ setHTMLFixture(`<div class="${MOCK_LAYOUT_PAGE_CLASS}"></div>`);
return shallowMountExtended(IssuableSidebarRoot, {
slots: {
@@ -38,6 +39,7 @@ describe('IssuableSidebarRoot', () => {
afterEach(() => {
wrapper.destroy();
+ resetHTMLFixture();
});
describe('when sidebar is expanded', () => {
diff --git a/spec/frontend/whats_new/utils/notification_spec.js b/spec/frontend/whats_new/utils/notification_spec.js
index ef61462a3c5..dac02ee07bd 100644
--- a/spec/frontend/whats_new/utils/notification_spec.js
+++ b/spec/frontend/whats_new/utils/notification_spec.js
@@ -1,3 +1,4 @@
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { setNotification, getVersionDigest } from '~/whats_new/utils/notification';
@@ -11,12 +12,13 @@ describe('~/whats_new/utils/notification', () => {
const getAppEl = () => wrapper.querySelector('.app');
beforeEach(() => {
- loadFixtures('static/whats_new_notification.html');
+ loadHTMLFixture('static/whats_new_notification.html');
wrapper = document.querySelector('.whats-new-notification-fixture-root');
});
afterEach(() => {
wrapper.remove();
+ resetHTMLFixture();
});
describe('setNotification', () => {
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
index c4e914bcf34..d8748c65da7 100644
--- a/spec/frontend/wikis_spec.js
+++ b/spec/frontend/wikis_spec.js
@@ -1,5 +1,5 @@
import { escape } from 'lodash';
-import { setHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import Wikis from '~/pages/shared/wikis/wikis';
import Tracking from '~/tracking';
@@ -21,6 +21,10 @@ describe('Wikis', () => {
Wikis.trackPageView();
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
it('sends the tracking event and context', () => {
expect(Tracking.event).toHaveBeenCalledWith(trackingPage, 'view_wiki_page', {
label: 'view_wiki_page',
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 13f221fd9d9..a88910b2613 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import Dropzone from 'dropzone';
import $ from 'jquery';
import Mousetrap from 'mousetrap';
+import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GLForm from '~/gl_form';
import * as utils from '~/lib/utils/common_utils';
import ZenMode from '~/zen_mode';
@@ -33,7 +34,7 @@ describe('ZenMode', () => {
mock = new MockAdapter(axios);
mock.onGet().reply(200);
- loadFixtures(fixtureName);
+ loadHTMLFixture(fixtureName);
const form = $('.js-new-note-form');
new GLForm(form); // eslint-disable-line no-new
@@ -47,6 +48,10 @@ describe('ZenMode', () => {
zen.scroll_position = 456;
});
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
describe('enabling dropzone', () => {
beforeEach(() => {
enterZen();
diff --git a/spec/frontend_integration/ide/ide_integration_spec.js b/spec/frontend_integration/ide/ide_integration_spec.js
index aad9b9e526c..a002ce91deb 100644
--- a/spec/frontend_integration/ide/ide_integration_spec.js
+++ b/spec/frontend_integration/ide/ide_integration_spec.js
@@ -1,4 +1,5 @@
import { nextTick } from 'vue';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { setTestTimeout } from 'helpers/timeout';
import waitForPromises from 'helpers/wait_for_promises';
import { waitForText } from 'helpers/wait_for_text';
@@ -17,13 +18,14 @@ describe('WebIDE', () => {
// For some reason these tests were timing out in CI.
// We will investigate in https://gitlab.com/gitlab-org/gitlab/-/issues/298714
setTestTimeout(20000);
- setFixtures('<div class="webide-container"></div>');
+ setHTMLFixture('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
});
afterEach(() => {
vm.$destroy();
vm = null;
+ resetHTMLFixture();
});
it('user commits changes', async () => {
diff --git a/spec/frontend_integration/ide/user_opens_file_spec.js b/spec/frontend_integration/ide/user_opens_file_spec.js
index 2cb3363ef85..c3131f6ad45 100644
--- a/spec/frontend_integration/ide/user_opens_file_spec.js
+++ b/spec/frontend_integration/ide/user_opens_file_spec.js
@@ -1,4 +1,5 @@
import { screen } from '@testing-library/dom';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import * as ideHelper from './helpers/ide_helper';
import startWebIDE from './helpers/start';
@@ -10,7 +11,7 @@ describe('IDE: User opens a file in the Web IDE', () => {
let container;
beforeEach(async () => {
- setFixtures('<div class="webide-container"></div>');
+ setHTMLFixture('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
vm = startWebIDE(container);
@@ -21,6 +22,7 @@ describe('IDE: User opens a file in the Web IDE', () => {
afterEach(() => {
vm.$destroy();
vm = null;
+ resetHTMLFixture();
});
describe('user opens a directory', () => {
diff --git a/spec/frontend_integration/ide/user_opens_ide_spec.js b/spec/frontend_integration/ide/user_opens_ide_spec.js
index c9d78d1de8f..b2b85452451 100644
--- a/spec/frontend_integration/ide/user_opens_ide_spec.js
+++ b/spec/frontend_integration/ide/user_opens_ide_spec.js
@@ -1,4 +1,5 @@
import { screen } from '@testing-library/dom';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import * as ideHelper from './helpers/ide_helper';
import startWebIDE from './helpers/start';
@@ -10,13 +11,14 @@ describe('IDE: User opens IDE', () => {
let container;
beforeEach(() => {
- setFixtures('<div class="webide-container"></div>');
+ setHTMLFixture('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
});
afterEach(() => {
vm.$destroy();
vm = null;
+ resetHTMLFixture();
});
it('shows loading indicator while the IDE is loading', async () => {
diff --git a/spec/frontend_integration/ide/user_opens_mr_spec.js b/spec/frontend_integration/ide/user_opens_mr_spec.js
index 3ffc5169351..084aae9f297 100644
--- a/spec/frontend_integration/ide/user_opens_mr_spec.js
+++ b/spec/frontend_integration/ide/user_opens_mr_spec.js
@@ -1,4 +1,5 @@
import { basename } from 'path';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { getMergeRequests, getMergeRequestWithChanges } from 'test_helpers/fixtures';
import { useOverclockTimers } from 'test_helpers/utils/overclock_timers';
import * as ideHelper from './helpers/ide_helper';
@@ -19,7 +20,7 @@ describe('IDE: User opens Merge Request', () => {
changes = getRelevantChanges();
- setFixtures('<div class="webide-container"></div>');
+ setHTMLFixture('<div class="webide-container"></div>');
container = document.querySelector('.webide-container');
vm = startWebIDE(container, { mrId });
@@ -31,6 +32,7 @@ describe('IDE: User opens Merge Request', () => {
afterEach(() => {
vm.$destroy();
vm = null;
+ resetHTMLFixture();
});
const findAllTabs = () => Array.from(document.querySelectorAll('.multi-file-tab'));
diff --git a/spec/frontend_integration/lib/utils/browser_spec.js b/spec/frontend_integration/lib/utils/browser_spec.js
index 6c72e29076d..c9e99af2889 100644
--- a/spec/frontend_integration/lib/utils/browser_spec.js
+++ b/spec/frontend_integration/lib/utils/browser_spec.js
@@ -1,4 +1,5 @@
import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import * as commonUtils from '~/lib/utils/common_utils';
describe('common_utils browser specific specs', () => {
@@ -14,7 +15,7 @@ describe('common_utils browser specific specs', () => {
it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => {
jest.spyOn(breakpointInstance, 'isDesktop').mockReturnValue(false);
- setFixtures(`
+ setHTMLFixture(`
<div class="diff-file file-title-flex-parent">
blah blah blah
</div>
@@ -24,12 +25,14 @@ describe('common_utils browser specific specs', () => {
`);
expect(commonUtils.contentTop()).toBe(0);
+
+ resetHTMLFixture();
});
it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => {
jest.spyOn(breakpointInstance, 'isDesktop').mockReturnValue(true);
- setFixtures(`
+ setHTMLFixture(`
<div class="diff-file file-title-flex-parent">
blah blah blah
</div>
@@ -41,6 +44,8 @@ describe('common_utils browser specific specs', () => {
mockOffsetHeight(document.querySelector('.diff-file'), 100);
mockOffsetHeight(document.querySelector('.mr-version-controls'), 18);
expect(commonUtils.contentTop()).toBe(18);
+
+ resetHTMLFixture();
});
});
diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb
index 67405ee3b21..90cf3cb03f8 100644
--- a/spec/helpers/projects/pipeline_helper_spec.rb
+++ b/spec/helpers/projects/pipeline_helper_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe Projects::PipelineHelper do
can_generate_codequality_reports: pipeline.can_generate_codequality_reports?.to_json,
graphql_resource_etag: graphql_etag_pipeline_path(pipeline),
metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: project.namespace, project_id: project, format: :json),
+ pipeline_iid: pipeline.iid,
pipeline_project_path: project.full_path
})
end
diff --git a/spec/lib/gitlab/application_rate_limiter_spec.rb b/spec/lib/gitlab/application_rate_limiter_spec.rb
index 20c89eab5f5..efe78cd3a35 100644
--- a/spec/lib/gitlab/application_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/application_rate_limiter_spec.rb
@@ -56,6 +56,20 @@ RSpec.describe Gitlab::ApplicationRateLimiter, :clean_gitlab_redis_rate_limiting
end
end
+ context 'when the key is valid' do
+ it 'records the checked key in request storage', :request_store do
+ subject.throttled?(:test_action, scope: [user])
+
+ expect(::Gitlab::Instrumentation::RateLimitingGates.payload)
+ .to eq(::Gitlab::Instrumentation::RateLimitingGates::GATES => [:test_action])
+
+ subject.throttled?(:another_action, scope: [user], peek: true)
+
+ expect(::Gitlab::Instrumentation::RateLimitingGates.payload)
+ .to eq(::Gitlab::Instrumentation::RateLimitingGates::GATES => [:test_action, :another_action])
+ end
+ end
+
shared_examples 'throttles based on key and scope' do
let(:start_time) { Time.current.beginning_of_hour }
diff --git a/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb b/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb
index 3f250d13e84..cffcda0a2ca 100644
--- a/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb
+++ b/spec/lib/gitlab/background_migration/expire_o_auth_tokens_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::BackgroundMigration::ExpireOAuthTokens, :migration, schem
sub_batch_size: 2,
pause_ms: 0,
connection: ActiveRecord::Base.connection)
- .perform(1000)
+ .perform
end
before do
diff --git a/spec/lib/gitlab/instrumentation/rate_limiting_gates_spec.rb b/spec/lib/gitlab/instrumentation/rate_limiting_gates_spec.rb
new file mode 100644
index 00000000000..ac308eb7c80
--- /dev/null
+++ b/spec/lib/gitlab/instrumentation/rate_limiting_gates_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Instrumentation::RateLimitingGates, :request_store do
+ describe '.gates' do
+ it 'returns an empty array when no gates are tracked' do
+ expect(described_class.gates).to eq([])
+ end
+
+ it 'returns all gates used in the request' do
+ described_class.track(:foo)
+
+ RequestStore.clear!
+
+ described_class.track(:bar)
+ described_class.track(:baz)
+
+ expect(described_class.gates).to contain_exactly(:bar, :baz)
+ end
+
+ it 'deduplicates its results' do
+ described_class.track(:foo)
+ described_class.track(:bar)
+ described_class.track(:foo)
+
+ expect(described_class.gates).to contain_exactly(:foo, :bar)
+ end
+ end
+
+ describe '.payload' do
+ it 'returns the gates in a hash' do
+ described_class.track(:foo)
+ described_class.track(:bar)
+
+ expect(described_class.payload).to eq(described_class::GATES => [:foo, :bar])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index a9663012e9a..5fea355ab4f 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -77,6 +77,27 @@ RSpec.describe Gitlab::InstrumentationHelper do
end
end
+ context 'rate-limiting gates' do
+ context 'when the request did not pass through any rate-limiting gates' do
+ it 'logs an empty array of gates' do
+ subject
+
+ expect(payload[:rate_limiting_gates]).to eq([])
+ end
+ end
+
+ context 'when the request passed through rate-limiting gates' do
+ it 'logs an array of gates used' do
+ Gitlab::Instrumentation::RateLimitingGates.track(:foo)
+ Gitlab::Instrumentation::RateLimitingGates.track(:bar)
+
+ subject
+
+ expect(payload[:rate_limiting_gates]).to contain_exactly(:foo, :bar)
+ end
+ end
+ end
+
it 'logs cpu_s duration' do
subject
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 210b9162be0..00ae55237e9 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -287,7 +287,8 @@ RSpec.describe Gitlab::SidekiqLogging::StructuredLogger do
'job_status' => 'done',
'duration_s' => 0.0,
'completed_at' => timestamp.to_f,
- 'cpu_s' => 1.111112
+ 'cpu_s' => 1.111112,
+ 'rate_limiting_gates' => []
)
end
diff --git a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
index e8c6fb790c3..61b60484692 100644
--- a/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
+++ b/spec/lib/sidebars/projects/menus/monitor_menu_spec.rb
@@ -72,6 +72,14 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
let(:item_id) { :logs }
it_behaves_like 'access rights checks'
+
+ context 'when feature disabled' do
+ before do
+ stub_feature_flags(monitor_logging: false)
+ end
+
+ specify { is_expected.to be_nil }
+ end
end
describe 'Tracing' do
diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb
index d4dc7a76516..7682cf39450 100644
--- a/spec/mailers/emails/merge_requests_spec.rb
+++ b/spec/mailers/emails/merge_requests_spec.rb
@@ -65,7 +65,9 @@ RSpec.describe Emails::MergeRequests do
is_expected.to have_body_text('due to conflict.')
is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request))
is_expected.to have_text_part_content(assignee.name)
+ is_expected.to have_html_part_content(assignee.name)
is_expected.to have_text_part_content(reviewer.name)
+ is_expected.to have_html_part_content(reviewer.name)
end
end
end
diff --git a/spec/migrations/20220428133724_schedule_expire_o_auth_tokens_spec.rb b/spec/migrations/20220513043344_reschedule_expire_o_auth_tokens_spec.rb
index 05d1053a46c..63fff279acc 100644
--- a/spec/migrations/20220428133724_schedule_expire_o_auth_tokens_spec.rb
+++ b/spec/migrations/20220513043344_reschedule_expire_o_auth_tokens_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require_migration!
-RSpec.describe ScheduleExpireOAuthTokens do
+RSpec.describe RescheduleExpireOAuthTokens do
let_it_be(:migration) { described_class::MIGRATION }
describe '#up' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 3fe7171728e..1e298191e56 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -3785,19 +3785,6 @@ RSpec.describe Ci::Build do
run_job_without_exception
end
-
- context 'when ci_reduce_persistent_ref_writes feature flag is disabled' do
- before do
- stub_feature_flags(ci_reduce_persistent_ref_writes: false)
- end
-
- it 'falls back to the previous behavior' do
- expect(job.pipeline).not_to receive(:ensure_persistent_ref)
- expect(job.pipeline.persistent_ref).to receive(:create).once
-
- run_job_without_exception
- end
- end
end
shared_examples 'saves data on transition' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 329b6bbe773..8dc041814fa 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1433,32 +1433,6 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
- context 'when ci_reduce_persistent_ref_writes feature flag is disabled' do
- before do
- stub_feature_flags(ci_reduce_persistent_ref_writes: false)
- end
-
- %w[succeed! drop! cancel! skip!].each do |action|
- context "when the pipeline recieved #{action} event" do
- it 'deletes a persistent ref' do
- expect(pipeline.persistent_ref).to receive(:delete).once
-
- pipeline.public_send(action)
- end
- end
- end
-
- %w[block! delay!].each do |action|
- context "when the pipeline recieved #{action} event" do
- it 'does not delete a persistent ref' do
- expect(pipeline.persistent_ref).not_to receive(:delete)
-
- pipeline.public_send(action)
- end
- end
- end
- end
-
describe 'synching status to Jira' do
let(:worker) { ::JiraConnect::SyncBuildsWorker }
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 036072aab76..67b58c7bf6f 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -5,138 +5,188 @@ require 'spec_helper'
RSpec.describe EventCollection do
include DesignManagementTestHelpers
- describe '#to_a' do
- let_it_be(:group) { create(:group) }
- let_it_be(:project) { create(:project_empty_repo, group: group) }
- let_it_be(:projects) { Project.where(id: project.id) }
- let_it_be(:user) { create(:user) }
- let_it_be(:merge_request) { create(:merge_request) }
-
- before do
- enable_design_management
- end
+ shared_examples 'EventCollection examples' do
+ describe '#to_a' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project_empty_repo, group: group) }
+ let_it_be(:projects) { Project.where(id: project.id) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:merge_request) { create(:merge_request) }
+
+ before do
+ enable_design_management
+ end
- context 'with project events' do
- let_it_be(:push_event_payloads) do
- Array.new(9) do
- create(:push_event_payload,
- event: create(:push_event, project: project, author: user))
+ context 'with project events' do
+ let_it_be(:push_event_payloads) do
+ Array.new(9) do
+ create(:push_event_payload,
+ event: create(:push_event, project: project, author: user))
+ end
end
- end
- let_it_be(:merge_request_events) { create_list(:event, 10, :commented, project: project, target: merge_request) }
- let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) }
- let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) }
- let_it_be(:design_event) { create(:design_event, project: project) }
-
- let(:push_events) { push_event_payloads.map(&:event) }
-
- it 'returns an Array of events', :aggregate_failures do
- most_recent_20_events = [
- wiki_page_event,
- design_event,
- closed_issue_event,
- *push_events,
- *merge_request_events
- ].sort_by(&:id).reverse.take(20)
- events = described_class.new(projects).to_a
-
- expect(events).to be_an_instance_of(Array)
- expect(events).to match_array(most_recent_20_events)
- end
+ let_it_be(:merge_request_events) { create_list(:event, 10, :merged, project: project, target: merge_request) }
+ let_it_be(:closed_issue_event) { create(:closed_issue_event, project: project, author: user) }
+ let_it_be(:wiki_page_event) { create(:wiki_page_event, project: project) }
+ let_it_be(:design_event) { create(:design_event, project: project) }
+
+ let(:push_events) { push_event_payloads.map(&:event) }
+
+ it 'returns an Array of all event types when no filter is passed', :aggregate_failures do
+ most_recent_20_events = [
+ wiki_page_event,
+ design_event,
+ closed_issue_event,
+ *push_events,
+ *merge_request_events
+ ].sort_by(&:id).reverse.take(20)
+ events = described_class.new(projects).to_a
+
+ expect(events).to be_an_instance_of(Array)
+ expect(events).to match_array(most_recent_20_events)
+ end
- it 'includes the wiki page events when using to_a' do
- events = described_class.new(projects).to_a
+ it 'includes the wiki page events when using to_a' do
+ events = described_class.new(projects).to_a
- expect(events).to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
+ end
- it 'includes the design events' do
- collection = described_class.new(projects)
+ it 'includes the design events' do
+ collection = described_class.new(projects)
- expect(collection.to_a).to include(design_event)
- expect(collection.all_project_events).to include(design_event)
- end
+ expect(collection.to_a).to include(design_event)
+ expect(collection.all_project_events).to include(design_event)
+ end
- it 'includes the wiki page events when using all_project_events' do
- events = described_class.new(projects).all_project_events
+ it 'includes the wiki page events when using all_project_events' do
+ events = described_class.new(projects).all_project_events
- expect(events).to include(wiki_page_event)
- end
+ expect(events).to include(wiki_page_event)
+ end
- it 'applies a limit to the number of events' do
- events = described_class.new(projects).to_a
+ it 'applies a limit to the number of events' do
+ events = described_class.new(projects).to_a
- expect(events.length).to eq(20)
- end
+ expect(events.length).to eq(20)
+ end
- it 'can paginate through events' do
- events = described_class.new(projects, limit: 5, offset: 15).to_a
+ it 'can paginate through events' do
+ events = described_class.new(projects, limit: 5, offset: 15).to_a
- expect(events.length).to eq(5)
- end
+ expect(events.length).to eq(5)
+ end
- it 'returns an empty Array when crossing the maximum page number' do
- events = described_class.new(projects, limit: 1, offset: 15).to_a
+ it 'returns an empty Array when crossing the maximum page number' do
+ events = described_class.new(projects, limit: 1, offset: 15).to_a
- expect(events).to be_empty
- end
+ expect(events).to be_empty
+ end
- it 'allows filtering of events using an EventFilter, returning single item' do
- filter = EventFilter.new(EventFilter::ISSUE)
- events = described_class.new(projects, filter: filter).to_a
+ it 'allows filtering of events using an EventFilter, returning single item' do
+ filter = EventFilter.new(EventFilter::ISSUE)
+ events = described_class.new(projects, filter: filter).to_a
- expect(events).to contain_exactly(closed_issue_event)
- end
+ expect(events).to contain_exactly(closed_issue_event)
+ end
- it 'allows filtering of events using an EventFilter, returning several items' do
- filter = EventFilter.new(EventFilter::COMMENTS)
- events = described_class.new(projects, filter: filter).to_a
+ it 'allows filtering of events using an EventFilter, returning several items' do
+ filter = EventFilter.new(EventFilter::MERGED)
+ events = described_class.new(projects, filter: filter).to_a
- expect(events).to match_array(merge_request_events)
- end
+ expect(events).to match_array(merge_request_events)
+ end
- it 'allows filtering of events using an EventFilter, returning pushes' do
- filter = EventFilter.new(EventFilter::PUSH)
- events = described_class.new(projects, filter: filter).to_a
+ it 'allows filtering of events using an EventFilter, returning pushes' do
+ filter = EventFilter.new(EventFilter::PUSH)
+ events = described_class.new(projects, filter: filter).to_a
- expect(events).to match_array(push_events)
+ expect(events).to match_array(push_events)
+ end
end
- end
- context 'with group events' do
- let(:groups) { group.self_and_descendants.public_or_visible_to_user(user) }
- let(:subject) { described_class.new(projects, groups: groups).to_a }
+ context 'with group events' do
+ let(:groups) { group.self_and_descendants.public_or_visible_to_user(user) }
+ let(:subject) { described_class.new(projects, groups: groups).to_a }
- it 'includes also group events' do
- subgroup = create(:group, parent: group)
- event1 = create(:event, project: project, author: user)
- event2 = create(:event, project: nil, group: group, author: user)
- event3 = create(:event, project: nil, group: subgroup, author: user)
+ it 'includes also group events' do
+ subgroup = create(:group, parent: group)
+ event1 = create(:event, project: project, author: user)
+ event2 = create(:event, project: nil, group: group, author: user)
+ event3 = create(:event, project: nil, group: subgroup, author: user)
- expect(subject).to eq([event3, event2, event1])
- end
+ expect(subject).to eq([event3, event2, event1])
+ end
- it 'does not include events from inaccessible groups' do
- subgroup = create(:group, :private, parent: group)
- event1 = create(:event, project: nil, group: group, author: user)
- create(:event, project: nil, group: subgroup, author: user)
+ it 'does not include events from inaccessible groups' do
+ subgroup = create(:group, :private, parent: group)
+ event1 = create(:event, project: nil, group: group, author: user)
+ create(:event, project: nil, group: subgroup, author: user)
- expect(subject).to eq([event1])
- end
+ expect(subject).to match_array([event1])
+ end
+
+ context 'pagination through events' do
+ let_it_be(:project_events) { create_list(:event, 10, project: project) }
+ let_it_be(:group_events) { create_list(:event, 10, group: group, author: user) }
- context 'pagination through events' do
- let_it_be(:project_events) { create_list(:event, 10, project: project) }
- let_it_be(:group_events) { create_list(:event, 10, group: group, author: user) }
+ let(:subject) { described_class.new(projects, limit: 10, offset: 5, groups: groups).to_a }
- let(:subject) { described_class.new(projects, limit: 10, offset: 5, groups: groups).to_a }
+ it 'returns recent groups and projects events' do
+ recent_events_with_offset = (project_events[5..] + group_events[..4]).reverse
- it 'returns recent groups and projects events' do
- recent_events_with_offset = (project_events[5..] + group_events[..4]).reverse
+ expect(subject).to eq(recent_events_with_offset)
+ end
+ end
- expect(subject).to eq(recent_events_with_offset)
+ context 'project exclusive event types' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:filter, :event) do
+ EventFilter::PUSH | lazy { create(:push_event, project: project) }
+ EventFilter::MERGED | lazy { create(:event, :merged, project: project, target: merge_request) }
+ EventFilter::TEAM | lazy { create(:event, :joined, project: project) }
+ EventFilter::ISSUE | lazy { create(:closed_issue_event, project: project) }
+ EventFilter::DESIGNS | lazy { create(:design_event, project: project) }
+ end
+
+ with_them do
+ let(:subject) do
+ described_class.new(projects, groups: Group.where(id: group.id), filter: EventFilter.new(filter))
+ end
+
+ it "queries only project events" do
+ expected_event = event # Forcing lazy evaluation
+ expect(subject).to receive(:project_events).with(no_args).and_call_original
+ expect(subject).not_to receive(:group_events)
+
+ expect(subject.to_a).to match_array(expected_event)
+ end
+ end
end
end
end
end
+
+ context 'when the optimized_project_and_group_activity_queries FF is on' do
+ before do
+ stub_feature_flags(optimized_project_and_group_activity_queries: true)
+ end
+
+ it_behaves_like 'EventCollection examples'
+
+ it 'returns no events if no projects are passed' do
+ events = described_class.new(Project.none).to_a
+
+ expect(events).to be_empty
+ end
+ end
+
+ context 'when the optimized_project_and_group_activity_queries FF is off' do
+ before do
+ stub_feature_flags(optimized_project_and_group_activity_queries: false)
+ end
+
+ it_behaves_like 'EventCollection examples'
+ end
end
diff --git a/spec/requests/admin/batched_jobs_controller_spec.rb b/spec/requests/admin/batched_jobs_controller_spec.rb
new file mode 100644
index 00000000000..9a0654c64b4
--- /dev/null
+++ b/spec/requests/admin/batched_jobs_controller_spec.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Admin::BatchedJobsController, :enable_admin_mode do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'GET #show' do
+ let(:main_database_job) { create(:batched_background_migration_job) }
+ let(:default_model) { ActiveRecord::Base }
+
+ it 'fetches the job' do
+ get admin_background_migration_batched_job_path(main_database_job.batched_migration, main_database_job)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'uses the default connection' do
+ expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(default_model.connection).and_yield
+
+ get admin_background_migration_batched_job_path(main_database_job.batched_migration, main_database_job)
+ end
+
+ it 'returns a default database record' do
+ get admin_background_migration_batched_job_path(main_database_job.batched_migration, main_database_job)
+
+ expect(assigns(:job)).to eql(main_database_job)
+ end
+
+ context 'when the job does not exist' do
+ let(:invalid_job) { non_existing_record_id }
+
+ it 'returns not found' do
+ get admin_background_migration_batched_job_path(main_database_job.batched_migration, invalid_job)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when multiple database is enabled', :add_ci_connection do
+ let(:base_models) { { 'fake_db' => default_model, 'ci' => ci_model } }
+ let(:ci_model) { Ci::ApplicationRecord }
+
+ before do
+ allow(Gitlab::Database).to receive(:database_base_models).and_return(base_models)
+ end
+
+ context 'when CI database is provided' do
+ it "uses CI database connection" do
+ expect(Gitlab::Database::SharedModel).to receive(:using_connection).with(ci_model.connection).and_yield
+
+ get admin_background_migration_batched_job_path(main_database_job.batched_migration, main_database_job,
+ database: 'ci')
+ end
+
+ it 'returns a CI database record' do
+ ci_database_job = Gitlab::Database::SharedModel.using_connection(ci_model.connection) do
+ create(:batched_background_migration_job, :failed)
+ end
+
+ get admin_background_migration_batched_job_path(ci_database_job.batched_migration,
+ ci_database_job, database: 'ci')
+
+ expect(assigns(:job)).to eql(ci_database_job)
+ expect(assigns(:job)).not_to eql(main_database_job)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
index dee8f80bc5d..22b5f2d5112 100644
--- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -109,7 +109,7 @@ RSpec.describe 'Adding a Note' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response).to include(
- 'errors' => [/Merged this merge request/],
+ 'errors' => include(/Merged this merge request/),
'note' => nil
)
end
diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb
index 0ff2c46e693..01f69f0aae2 100644
--- a/spec/requests/api/personal_access_tokens_spec.rb
+++ b/spec/requests/api/personal_access_tokens_spec.rb
@@ -73,6 +73,54 @@ RSpec.describe API::PersonalAccessTokens do
end
end
+ describe 'DELETE /personal_access_tokens/self' do
+ let(:path) { '/personal_access_tokens/self' }
+ let(:token) { create(:personal_access_token, user: current_user) }
+
+ subject { delete api(path, current_user, personal_access_token: token) }
+
+ shared_examples 'revoking token succeeds' do
+ it 'revokes token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ expect(token.reload).to be_revoked
+ end
+ end
+
+ shared_examples 'revoking token denied' do |status|
+ it 'cannot revoke token' do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+
+ context 'when current_user is an administrator', :enable_admin_mode do
+ let(:current_user) { create(:admin) }
+
+ it_behaves_like 'revoking token succeeds'
+ end
+
+ context 'when current_user is not an administrator' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'revoking token succeeds'
+
+ context 'with impersonated token' do
+ let(:token) { create(:personal_access_token, :impersonation, user: current_user) }
+
+ it_behaves_like 'revoking token denied', :bad_request
+ end
+
+ context 'with already revoked token' do
+ let(:token) { create(:personal_access_token, :revoked, user: current_user) }
+
+ it_behaves_like 'revoking token denied', :unauthorized
+ end
+ end
+ end
+
describe 'DELETE /personal_access_tokens/:id' do
let(:path) { "/personal_access_tokens/#{token1.id}" }
diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb
index ee1388024ea..514828e3c69 100644
--- a/spec/serializers/cluster_entity_spec.rb
+++ b/spec/serializers/cluster_entity_spec.rb
@@ -77,6 +77,14 @@ RSpec.describe ClusterEntity do
expect(subject[:gitlab_managed_apps_logs_path]).to eq(log_explorer_path)
end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(monitor_logging: false)
+ end
+
+ specify { is_expected.not_to include(:gitlab_managed_apps_logs_path) }
+ end
end
context 'enable_advanced_logs_querying' do
@@ -98,6 +106,14 @@ RSpec.describe ClusterEntity do
expect(subject[:enable_advanced_logs_querying]).to be true
end
end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(monitor_logging: false)
+ end
+
+ specify { is_expected.not_to include(:enable_advanced_logs_querying) }
+ end
end
end
end
diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb
index a59107ad309..9b6a293da16 100644
--- a/spec/serializers/environment_entity_spec.rb
+++ b/spec/serializers/environment_entity_spec.rb
@@ -166,6 +166,18 @@ RSpec.describe EnvironmentEntity do
expect(subject[:logs_api_path]).to eq(elasticsearch_project_logs_path(project, environment_name: environment.name, format: :json))
end
+
+ context 'with feature flag disabled' do
+ before do
+ stub_feature_flags(monitor_logging: false)
+ end
+
+ it 'does not expose logs keys' do
+ expect(subject).not_to include(:logs_path)
+ expect(subject).not_to include(:logs_api_path)
+ expect(subject).not_to include(:enable_advanced_logs_querying)
+ end
+ end
end
end
diff --git a/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb
index 63930c79c42..ab4ba20e716 100644
--- a/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb
+++ b/spec/services/ci/pipeline_creation/start_pipeline_service_spec.rb
@@ -22,17 +22,5 @@ RSpec.describe Ci::PipelineCreation::StartPipelineService do
service.execute
end
-
- context 'when ci_reduce_persistent_ref_writes feature flag is disabled' do
- before do
- stub_feature_flags(ci_reduce_persistent_ref_writes: false)
- end
-
- it 'does not populate pipeline ref' do
- expect(pipeline.persistent_ref).not_to receive(:create)
-
- service.execute
- end
- end
end
end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index c15486dc916..7164ba8fac0 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -346,7 +346,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
end
it 'sets state as attention_requested' do
- update_merge_request(reviewer_ids: [user.id])
+ update_merge_request(reviewer_ids: [user2.id])
expect(merge_request.merge_request_reviewers[0].state).to eq('attention_requested')
expect(merge_request.merge_request_reviewers[0].updated_state_by).to eq(user)
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index a6428bf8573..50b8083ebb3 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -21,6 +21,14 @@ module Spec
click_link(value)
end
end
+
+ # pajamas_sort_by is used to sort new pajamas dropdowns. When
+ # all of the dropdowns are converted, pajamas_sort_by can be renamed to sort_by
+ # https://gitlab.com/groups/gitlab-org/-/epics/7551
+ def pajamas_sort_by(value)
+ find('.filter-dropdown-container .dropdown').click
+ find('.dropdown-item', text: value).click
+ end
end
end
end
diff --git a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
index b7966e25b38..7d51c90522a 100644
--- a/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/sidekiq_logging/structured_logger_shared_context.rb
@@ -57,7 +57,8 @@ RSpec.shared_context 'structured_logger' do
'job_status' => 'done',
'duration_s' => 0.0,
'completed_at' => timestamp.to_f,
- 'cpu_s' => 1.111112
+ 'cpu_s' => 1.111112,
+ 'rate_limiting_gates' => []
)
end
diff --git a/yarn.lock b/yarn.lock
index deacf960e31..542e28571c8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -968,14 +968,14 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.14.0.tgz#92b36bc98ccbed49a4dbca310862146275091cb2"
integrity sha512-U9EYmEIiTMl7R3X5DmCrw6fz7gz8c1kjvQtaF6HfJ15xDtR7trRAyCNbn3z7YGk1QJ8Cv/Ifw2/T5SxXwYd7dw==
-"@gitlab/ui@40.2.0":
- version "40.2.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-40.2.0.tgz#798630112809816afa0d37f58d567e8f1ad53f8e"
- integrity sha512-3AbCh0UVB5xEUoPrwr2YkzM9IrNOW3LFmyYCXEuVTp7whHyHG14T+sty3YDQWlOFjjEdMD4fU2iXveq2V3cq0A==
+"@gitlab/ui@40.2.1":
+ version "40.2.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-40.2.1.tgz#510ea1cda0a62afbfb0bc6a74b56e1128ddef428"
+ integrity sha512-dDsyu8Zuf5MYZwx6A6m2TeIPJL+ytTP7J0x0M8649MOqJJB2/3pq8IfcowWSQAvpO57w5N+G/QlotNypZ3e31w==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"
- dompurify "^2.3.6"
+ dompurify "^2.3.7"
echarts "^5.2.1"
iframe-resizer "^4.3.2"
lodash "^4.17.20"
@@ -5024,7 +5024,7 @@ dompurify@2.3.6:
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==
-dompurify@^2.3.6, dompurify@^2.3.7:
+dompurify@^2.3.7:
version "2.3.7"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.7.tgz#30aa7feddc1420f3e554b1b3fe9e2f318a28066e"
integrity sha512-fsVZLywBd3awZIG3qU4JEdw7DCb0uUCajTfWRrLhsgKjTBd5CIIluPoAkNfco05GuNYQGj4/+bQIhlq96eT9eQ==